public VLCWrapperParsingUnit(string identifier, Reference <WebTranscodingInfo> save, WebMediaInfo info, long position)
 {
     this.identifier = identifier;
     this.data       = save;
     this.info       = info;
     this.position   = position;
 }
Esempio n. 2
0
        public static WebMediaInfo GetMediaInfo(MediaSource source)
        {
            if (source.MediaType == WebStreamMediaType.TV)
            {
                // cache tv files for 10 seconds
                if (TvCache.ContainsKey(source.Id) && TvCache[source.Id].Item1.AddSeconds(10).CompareTo(DateTime.Now) > 0)
                {
                    // cache is valid, use it
                    return(TvCache[source.Id].Item2);
                }

                // get media info and save it to the cache
                TsBuffer     buf  = new TsBuffer(source.Id);
                WebMediaInfo info = GetMediaInfo(buf.GetCurrentFilePath(), true);
                TvCache[source.Id] = new Tuple <DateTime, WebMediaInfo>(DateTime.Now, info);
                return(info);
            }
            else if (!source.Exists)
            {
                throw new FileNotFoundException();
            }
            else if (source.SupportsDirectAccess)
            {
                using (var impersonator = source.GetImpersonator())
                {
                    return(GetMediaInfo(source.GetPath(), false));
                }
            }
            else
            {
                // not (yet?) supported
                throw new NotSupportedException();
            }
        }
Esempio n. 3
0
 public void Save(MediaSource src, WebMediaInfo info)
 {
     lock (cache)
     {
         cache[src.GetUniqueIdentifier()] = info;
     }
     isDirty = true;
 }
Esempio n. 4
0
        private static string GetFullInfoString(WebMediaInfo info, long fileSize)
        {
            int    index    = 0;
            double realSize = fileSize;

            while (realSize > 1024)
            {
                index++;
                realSize /= 1024;
            }
            return(String.Format("{0}, {1:#.#} {2}", GetShortQualityName(info), realSize, units[index]));
        }
Esempio n. 5
0
        public static WebMediaInfo GetMediaInfo(MediaSource source)
        {
            // Timeshiftings are a special case, as they can't be cached and need an additional path resolving step
            if (source.MediaType == WebMediaType.TV)
            {
                if (tvCache.ContainsKey(source.Id) && DateTime.Now - tvCache[source.Id].Item1 > TimeSpan.FromSeconds(60))
                {
                    return(tvCache[source.Id].Item2);
                }

                TsBuffer tsBuffer = new TsBuffer(source.Id);
                Log.Debug("Using path {0} from TS buffer {1} as source for {2}", tsBuffer.GetCurrentFilePath(), source.Id, source.GetDebugName());
                WebMediaInfo info = LoadMediaInfo(tsBuffer.GetCurrentFilePath());
                tvCache[source.Id] = new Tuple <DateTime, WebMediaInfo>(DateTime.Now, info);
                return(info);
            }

            using (var context = source.CreateNetworkContext())
            {
                // verify the file actually exists and is accessible over the local file system
                if (!source.Exists)
                {
                    Log.Warn("Trying to load MediaInfo for {0}, which does not exist or is inaccessible", source.GetDebugName());
                    return(null);
                }
                else if (!source.SupportsDirectAccess)
                {
                    Log.Warn("Loading MediaInfo for non-direct access source {0} isn't supported yet", source.GetDebugName());
                    return(null);
                }

                // if we got the file in the cache, return it if we have it and the file hasn't been changed
                var fileInfo = source.GetFileInfo();
                if (source.MediaType != WebMediaType.TV && persistentCache.HasForSource(source))
                {
                    var cachedItem = persistentCache.GetForSource(source);
                    if (cachedItem.Size == fileInfo.Size && cachedItem.CachedDate >= fileInfo.LastModifiedTime)
                    {
                        return(cachedItem.Info);
                    }
                }

                var info = LoadMediaInfo(context.RewritePath(source.GetPath()));
                if (info != null)
                {
                    persistentCache.Save(source, new CachedInfoWrapper(info, fileInfo));
                }

                return(info);
            }
        }
Esempio n. 6
0
        public static WebMediaInfo GetMediaInfo(MediaSource source)
        {
            // we can't use our persistent cache for TV unfortunately, but we do cache them in memory for 60 seconds
            if (source.MediaType == WebMediaType.TV)
            {
                if (tvCache.ContainsKey(source.Id) && DateTime.Now - tvCache[source.Id].Item1 > TimeSpan.FromSeconds(60))
                {
                    return(tvCache[source.Id].Item2);
                }

                // save it to our memory cache for a while
                TsBuffer buf  = new TsBuffer(source.Id);
                string   path = buf.GetCurrentFilePath();
                Log.Debug("Using path {0} from TS buffer {1} as source for {2}", path, source.Id, source.GetDebugName());
                WebMediaInfo info = DoLoadMediaInfo(buf.GetCurrentFilePath(), true);
                tvCache[source.Id] = new Tuple <DateTime, WebMediaInfo>(DateTime.Now, info);
                return(info);
            }

            // load this item from persistent disk cache, if possible
            if (persistentCache.HasForSource(source))
            {
                return(persistentCache.GetForSource(source));
            }

            // some checks that only matter when we are actually going to load it from disk
            if (!source.Exists)
            {
                Log.Warn("Trying to load mediainfo for {0}, which doesn't seem to exist", source.GetDebugName());
                return(null);
            }
            else if (!source.SupportsDirectAccess)
            {
                // not (yet?) supported
                Log.Warn("Loading mediainfo for non-direct access source {0} isn't supported yet", source.GetDebugName());
                return(null);
            }

            // actually load it
            WebMediaInfo outInfo;

            using (var impersonator = source.GetImpersonator())
            {
                outInfo = DoLoadMediaInfo(source.GetPath(), false);
            }
            if (outInfo != null)
            {
                persistentCache.Save(source, outInfo);
            }
            return(outInfo);
        }
Esempio n. 7
0
        public static string GetShortQualityName(WebMediaInfo info)
        {
            WebVideoStream vidStream = info.VideoStreams.First();

            if (vidStream.Width >= 1920 || vidStream.Height >= 1080)
            {
                return("1080p");
            }
            if (vidStream.Width >= 1280 || vidStream.Height >= 720)
            {
                return("720p");
            }
            return("SD");
        }
Esempio n. 8
0
        private void LoadMediaInfo(WebMediaInfo info)
        {
            if (info == null)
            {
                Log("No MediaInfo available");
                return;
            }

            mInfo = info;
            Log(String.Format("MediaInfo: streams: {0} video, {1} audio, {2} subtitle",
                              info.VideoStreams.Count, info.AudioStreams.Count, info.SubtitleStreams.Count));
            if (info.VideoStreams.Count() > 0)
            {
                WebVideoStream vid = info.VideoStreams.First();
                Log(String.Format("MediaInfo: video {0}x{1}; {2}; {3}", vid.Width, vid.Height, vid.DisplayAspectRatioString, vid.Codec));
            }

            // audio
            cbLanguage.Items.Clear();
            int i = 1;

            foreach (WebAudioStream audio in info.AudioStreams)
            {
                string name = "Audio stream #" + i++;
                if (!String.IsNullOrEmpty(audio.Language))
                {
                    name += ": " + audio.Language;
                }
                if (!String.IsNullOrEmpty(audio.Title))
                {
                    name += ": " + audio.Title;
                }
                cbLanguage.Items.Add(name);
            }

            // subtitle
            cbSubtitle.Items.Clear();
            i = 1;
            foreach (WebSubtitleStream stream in info.SubtitleStreams)
            {
                string name = "Subtitle stream #" + i++ + " (" + stream.ID + ")";
                if (!String.IsNullOrEmpty(stream.Language))
                {
                    name += ": " + stream.Language;
                }
                cbSubtitle.Items.Add(name);
            }
        }
Esempio n. 9
0
        public static decimal DEFAULT_ASPECT_RATIO = (decimal)16 / 9; // most new material is 16:9 these days

        public static WebMediaInfo LoadMediaInfoOrSurrogate(MediaSource source)
        {
            WebMediaInfo info;

            try
            {
                info = MediaInfoWrapper.GetMediaInfo(source);
                if (info != null)
                {
                    return(info);
                }
            }
            catch (Exception ex)
            {
                Log.Warn(String.Format("Failed to load MediaInfo for {0}", source.GetDebugName()), ex);
            }

            WebMediaInfo surr = new WebMediaInfo();

            surr.Duration        = 0;
            surr.SubtitleStreams = new List <WebSubtitleStream>();
            surr.AudioStreams    = new List <WebAudioStream>()
            {
                new WebAudioStream()
                {
                    Channels     = 2,
                    Codec        = "Unknown",
                    ID           = 1,
                    Index        = 0,
                    Language     = "und", // yes, that's valid ISO 639 (I think)
                    LanguageFull = "Unknown",
                }
            };
            surr.VideoStreams = new List <WebVideoStream>()
            {
                new WebVideoStream()
                {
                    Codec = "Unknown",
                    DisplayAspectRatio       = DEFAULT_ASPECT_RATIO,
                    DisplayAspectRatioString = "16:9",
                    ID     = 2,
                    Index  = 0,
                    Height = 720,
                    Width  = 1280
                }
            };
            return(surr);
        }
Esempio n. 10
0
        public static string GetShortQualityName(WebMediaInfo info)
        {
            if (!info.VideoStreams.Any())
            {
                return(UIStrings.Unknown);
            }

            WebVideoStream vidStream = info.VideoStreams.First();

            if (vidStream.Width >= 1920 || vidStream.Height >= 1080)
            {
                return(vidStream.Interlaced ? "1080i" : "1080p");
            }
            if (vidStream.Width >= 1280 || vidStream.Height >= 720)
            {
                return(vidStream.Interlaced ? "720i" : "720p");
            }
            return("SD");
        }
Esempio n. 11
0
        public static WebMediaInfo LoadMediaInfoOrSurrogate(MediaSource source)
        {
            WebMediaInfo info = MediaInfoWrapper.GetMediaInfo(source);

            if (info != null)
            {
                return(info);
            }

            WebMediaInfo surr = new WebMediaInfo();

            surr.Duration        = 0;
            surr.SubtitleStreams = new List <WebSubtitleStream>();
            surr.AudioStreams    = new List <WebAudioStream>()
            {
                new WebAudioStream()
                {
                    Channels     = 2,
                    Codec        = "Unknown",
                    ID           = 1,
                    Index        = 0,
                    Language     = "und", // yes, that's valid ISO 639 (I think)
                    LanguageFull = "Unknown",
                }
            };
            surr.VideoStreams = new List <WebVideoStream>()
            {
                new WebVideoStream()
                {
                    Codec = "Unknown",
                    DisplayAspectRatio       = 16 / 9, // this class is primarily used for TV data and that's mostly 16:9 these days afaik
                    DisplayAspectRatioString = "16:9",
                    ID     = 2,
                    Index  = 0,
                    Height = 1280, // gives this any problems?
                    Width  = 720
                }
            };
            return(surr);
        }
Esempio n. 12
0
 public CachedInfoWrapper(WebMediaInfo mediaInfo, WebFileInfo fileInfo)
 {
     CachedDate = DateTime.Now;
     Size       = fileInfo.Size;
     Info       = mediaInfo;
 }
Esempio n. 13
0
        public Resolution CalculateSize(TranscoderProfile profile, MediaSource source, WebMediaInfo info = null)
        {
            if (!profile.HasVideoStream)
            {
                return(new Resolution(0, 0));
            }

            decimal aspect = (decimal)16 / 9; // the default aspect ratio

            if (source.MediaType != WebStreamMediaType.TV && profile != null)
            {
                if (info == null)
                {
                    info = MediaInfoWrapper.GetMediaInfo(source);
                }

                if (info.VideoStreams.Count > 0)
                {
                    aspect = info.VideoStreams.First().DisplayAspectRatio;
                }
            }

            return(Resolution.Calculate(aspect, profile.MaxOutputWidth, profile.MaxOutputHeight, 2));
        }
Esempio n. 14
0
 public VLCWrapperParsingUnit(Reference <WebTranscodingInfo> save, WebMediaInfo info, int position)
 {
     data          = save;
     this.info     = info;
     this.position = position;
 }
Esempio n. 15
0
        private static WebMediaInfo DoLoadMediaInfo(string source, bool ignoreMemoryCache)
        {
            try
            {
                if (source == null || !File.Exists(source))
                {
                    Log.Warn("GetMediaInfo: File {0} doesn't exist or is not accessible", source);
                    return(null);
                }

                // check cache
                if (!ignoreMemoryCache && memoryCache.ContainsKey(source))
                {
                    return(memoryCache[source]);
                }

                /* Loosely based upon MediaInfoWrapper.cs (mediaportal/Core/Player) from MediaPortal trunk r27491 as of 15 June 2011
                 *
                 * Using the whole wrapper from MediaPortal is quite much porting work as it's cluttered with calls to other MP code. Referencing
                 * MediaPortal.Core.dll also isn't an option as that pulls in a big tree of dependencies.
                 *
                 * TODO: Needs error handling
                 * TODO: No support for DVDs yet
                 * TODO: Aspect ratio doesn't work properly yet
                 */
                MediaInfo info = new MediaInfo();
                info.Option("ParseSpeed", "0.3");
                info.Open(source);
                WebMediaInfo retinfo = new WebMediaInfo();
                retinfo.Container = info.Get(StreamKind.General, 0, "Format");

                // video
                retinfo.VideoStreams = new List <WebVideoStream>();
                int videoStreams = info.Count_Get(StreamKind.Video);
                for (int i = 0; i < videoStreams; i++)
                {
                    retinfo.Duration = retinfo.Duration == 0 ? (long)StringToInt(info.Get(StreamKind.Video, i, "Duration")) : retinfo.Duration;

                    string val = info.Get(StreamKind.Video, i, "DisplayAspectRatio");
                    retinfo.VideoStreams.Add(new WebVideoStream()
                    {
                        Codec = info.Get(StreamKind.Video, i, "Codec/String"),
                        DisplayAspectRatio       = StringToDecimal(info.Get(StreamKind.Video, i, "DisplayAspectRatio")),
                        DisplayAspectRatioString = info.Get(StreamKind.Video, i, "DisplayAspectRatio/String"),
                        Width  = StringToInt(info.Get(StreamKind.Video, i, "Width")),
                        Height = StringToInt(info.Get(StreamKind.Video, i, "Height")),
                        ID     = StringToInt(info.Get(StreamKind.Video, i, "ID")),
                        Index  = i
                    });
                }

                // audio codecs
                retinfo.AudioStreams = new List <WebAudioStream>();
                int audioStreams = info.Count_Get(StreamKind.Audio);
                for (int i = 0; i < audioStreams; i++)
                {
                    retinfo.Duration = retinfo.Duration == 0 ? (long)StringToInt(info.Get(StreamKind.Audio, i, "Duration")) : retinfo.Duration;
                    retinfo.AudioStreams.Add(new WebAudioStream()
                    {
                        Channels     = StringToInt(info.Get(StreamKind.Audio, i, "Channels")),
                        Codec        = info.Get(StreamKind.Audio, i, "Codec/String"),
                        ID           = StringToInt(info.Get(StreamKind.Audio, i, "ID")),
                        Language     = info.Get(StreamKind.Audio, i, "Language"),
                        LanguageFull = info.Get(StreamKind.Audio, i, "Language/String"),
                        Title        = info.Get(StreamKind.Audio, i, "Title"),
                        Index        = i
                    });
                }

                // subtitle codecs
                retinfo.SubtitleStreams = new List <WebSubtitleStream>();
                int subtitleStreams = info.Count_Get(StreamKind.Text);
                int scodecnr        = 0;
                for (scodecnr = 0; scodecnr < subtitleStreams; scodecnr++)
                {
                    retinfo.SubtitleStreams.Add(new WebSubtitleStream()
                    {
                        Language     = info.Get(StreamKind.Text, scodecnr, "Language"),
                        LanguageFull = info.Get(StreamKind.Text, scodecnr, "Language/String"),
                        ID           = StringToInt(info.Get(StreamKind.Text, scodecnr, "ID")),
                        Index        = scodecnr,
                        Filename     = null
                    });
                }

                // get max subtitle id
                var list   = retinfo.SubtitleStreams.Select(x => x.ID);
                int lastId = list.Count() == 0 ? 0 : list.Max();

                // standard name of external subtitle files
                string subfile = Path.Combine(Path.GetDirectoryName(source), Path.GetFileNameWithoutExtension(source) + ".srt");
                if (File.Exists(subfile))
                {
                    retinfo.SubtitleStreams.Add(new WebSubtitleStream()
                    {
                        Language     = "ext",
                        LanguageFull = "External subtitle file",
                        ID           = ++lastId, // a file with so many streams seems quite unlikely to me
                        Index        = ++scodecnr,
                        Filename     = subfile
                    });
                }

                // language in subtitle filename
                var files = Directory.GetFiles(Path.GetDirectoryName(source), Path.GetFileNameWithoutExtension(source) + ".*.srt");
                foreach (var file in files)
                {
                    string basename = Path.GetFileName(file).Substring(Path.GetFileNameWithoutExtension(source).Length);
                    string tag      = basename.Substring(1, basename.Length - 5);
                    retinfo.SubtitleStreams.Add(new WebSubtitleStream()
                    {
                        Language     = LookupCountryCode(tag),
                        LanguageFull = tag,
                        ID           = ++lastId,
                        Index        = ++scodecnr,
                        Filename     = file
                    });
                }


                // return
                info.Close();

                if (!memoryCache.ContainsKey(source))
                {
                    memoryCache.Add(source, retinfo);
                }
                else
                {
                    memoryCache[source] = retinfo;
                }

                return(retinfo);
            }
            catch (Exception ex)
            {
                Log.Error("Error parsing MediaInfo for " + source, ex);
                return(null);
            }
        }
Esempio n. 16
0
        public Resolution CalculateSize(TranscoderProfile profile, MediaSource source, WebMediaInfo info = null)
        {
            try
            {
                if (!profile.HasVideoStream)
                {
                    return(new Resolution(0, 0));
                }

                if (info == null)
                {
                    info = MediaInfoHelper.LoadMediaInfoOrSurrogate(source);
                }

                if (info.VideoStreams.Count > 0)
                {
                    var res = Resolution.Calculate(info.VideoStreams.First().DisplayAspectRatio, profile.MaxOutputWidth, profile.MaxOutputHeight, 2);
                    if (res.Width == 0 && res.Height == 0)
                    {
                        return(new Resolution(info.VideoStreams.First().Width, info.VideoStreams.First().Height));
                    }
                    return(res);
                }
            }
            catch (Exception ex)
            {
                Log.Warn("Failed to calculate size of output stream", ex);
            }

            // default
            return(Resolution.Calculate(MediaInfoHelper.DEFAULT_ASPECT_RATIO, profile.MaxOutputWidth, profile.MaxOutputHeight, 2));
        }
Esempio n. 17
0
 public static string GetFullInfoString(WebMediaInfo info, WebFileInfo fileInfo)
 {
     return(GetFullInfoString(info, fileInfo.Size));
 }
Esempio n. 18
0
 public static string GetFullInfoString(WebMediaInfo info, WebRecordingFileInfo fileInfo)
 {
     return(GetFullInfoString(fileInfo.Exists, info, fileInfo.Size));
 }
Esempio n. 19
0
 public static string GetFullInfoString(bool accessible, WebMediaInfo info, WebRecordingFileInfo fileInfo)
 {
     return(GetFullInfoString(accessible, info, fileInfo.Size));
 }
Esempio n. 20
0
 public static string GetShortQualityName(bool accessible, WebMediaInfo info)
 {
     return(accessible ? GetShortQualityName(info) : UIStrings.FileInaccessible);
 }
Esempio n. 21
0
        public static async Task <WebMediaInfo> ProcessAsync(IOwinContext context, string itemId, WebMediaType type)
        {
            if (itemId == null)
            {
                throw new BadRequestException("GetMediaInfo: itemId is null");
            }

            Guid                     mediaItemId;
            MediaItem                item;
            long                     duration  = 0;
            string                   container = string.Empty;
            MetadataContainer        info;
            List <WebVideoStream>    webVideoStreams    = new List <WebVideoStream>();
            List <WebAudioStream>    webAudioStreams    = new List <WebAudioStream>();
            List <WebSubtitleStream> webSubtitleStreams = new List <WebSubtitleStream>();

            if (type == WebMediaType.TV || type == WebMediaType.Radio)
            {
                int channelIdInt;
                if (int.TryParse(itemId, out channelIdInt))
                {
                    item = new LiveTvMediaItem(Guid.Empty);
                    info = MediaAnalyzer.ParseChannelStreamAsync(channelIdInt, (LiveTvMediaItem)item).Result;
                    if (info == null)
                    {
                        throw new BadRequestException(String.Format("GetMediaInfo: Channel {0} stream not available", itemId));
                    }
                }
                else
                {
                    throw new BadRequestException(String.Format("GetMediaInfo: Channel {0} not found", itemId));
                }
            }
            else if (Guid.TryParse(itemId, out mediaItemId) == true)
            {
                ISet <Guid> necessaryMIATypes = new HashSet <Guid>();
                necessaryMIATypes.Add(MediaAspect.ASPECT_ID);
                necessaryMIATypes.Add(ProviderResourceAspect.ASPECT_ID);
                necessaryMIATypes.Add(ImporterAspect.ASPECT_ID);

                ISet <Guid> optionalMIATypes = new HashSet <Guid>();
                optionalMIATypes.Add(VideoAspect.ASPECT_ID);
                optionalMIATypes.Add(VideoStreamAspect.ASPECT_ID);
                optionalMIATypes.Add(VideoAudioStreamAspect.ASPECT_ID);
                optionalMIATypes.Add(AudioAspect.ASPECT_ID);
                optionalMIATypes.Add(ImageAspect.ASPECT_ID);

                item = MediaLibraryAccess.GetMediaItemById(context, itemId, necessaryMIATypes, optionalMIATypes);

                if (item == null)
                {
                    throw new BadRequestException(String.Format("GetMediaInfo: No MediaItem found with id: {0}", itemId));
                }
            }
            else
            {
                throw new BadRequestException(String.Format("GetMediaInfo: Media not found with id: {0}", itemId));
            }

            // decide which type of media item we have
            info = await MediaAnalyzer.ParseMediaItemAsync(item);

            if (item.Aspects.ContainsKey(VideoAspect.ASPECT_ID))
            {
                var videoAspect       = item.GetAspect(VideoAspect.Metadata);
                var videoStreamAspect = item.GetAspect(VideoStreamAspect.Metadata);
                duration = Convert.ToInt64(videoStreamAspect.GetAttributeValue <long?>(VideoStreamAspect.ATTR_DURATION) ?? 0);
                bool interlaced = (videoStreamAspect.GetAttributeValue <string>(VideoStreamAspect.ATTR_VIDEO_TYPE) ?? "").ToLowerInvariant().Contains("interlaced");

                foreach (var data in info.Metadata)
                {
                    int edition       = data.Key;
                    int editionOffset = 0;
                    if (edition >= 0)
                    {
                        editionOffset = (edition + 1) * ProfileMediaItem.EDITION_OFFSET;
                    }

                    if (info.IsVideo(edition))
                    {
                        // Video
                        WebVideoStream webVideoStream = new WebVideoStream();
                        webVideoStream.Codec = Convert.ToString(info.Video[edition].Codec);
                        webVideoStream.DisplayAspectRatio       = Convert.ToDecimal(info.Video[edition].AspectRatio ?? 0);
                        webVideoStream.DisplayAspectRatioString = AspectRatioHelper.AspectRatioToString(Convert.ToDecimal(info.Video[edition].AspectRatio ?? 0));
                        webVideoStream.Height = Convert.ToInt32(info.Video[edition].Height ?? 0);
                        webVideoStream.Width  = Convert.ToInt32(info.Video[edition].Width ?? 0);
                        webVideoStreams.Add(webVideoStream);
                        webVideoStream.ID         = info.Video[edition].StreamIndex + editionOffset;
                        webVideoStream.Index      = 0;
                        webVideoStream.Interlaced = interlaced;

                        container = info.Metadata[edition].VideoContainerType.ToString();

                        // Audio
                        for (int i = 0; i < info.Audio[edition].Count; i++)
                        {
                            WebAudioStream webAudioStream = new WebAudioStream();
                            if (info.Audio[edition][i].Channels != null)
                            {
                                webAudioStream.Channels = info.Audio[edition][i].Channels.Value;
                            }

                            webAudioStream.Codec = info.Audio[edition][i].Codec.ToString();
                            webAudioStream.ID    = info.Audio[edition][i].StreamIndex + editionOffset;
                            webAudioStream.Index = i;
                            if (info.Audio[edition][i].Language != null)
                            {
                                string language = info.Audio[edition][i].Language == string.Empty ? UNDEFINED : info.Audio[edition][i].Language;
                                webAudioStream.Language = language;
                                if (language != UNDEFINED)
                                {
                                    webAudioStream.LanguageFull = new CultureInfo(language).EnglishName;
                                    if (item.HasEditions && item.Editions.Any(e => e.Value.SetNo == edition))
                                    {
                                        webAudioStream.Title = item.Editions.First(e => e.Key == edition).Value.Name;
                                    }
                                    else if (item.GetPlayData(out var mime, out var mediaName))
                                    {
                                        webAudioStream.Title = mediaName;
                                    }
                                    else
                                    {
                                        webAudioStream.Title = "?";
                                    }
                                    if (string.IsNullOrEmpty(webAudioStream.Codec) == false)
                                    {
                                        webAudioStream.Title += webAudioStream.Title?.Length > 0 ? $" ({webAudioStream.Codec.ToUpperInvariant()})" : webAudioStream.Codec.ToUpperInvariant();
                                    }
                                }
                            }
                            webAudioStreams.Add(webAudioStream);
                        }

                        // Subtitles
                        if (info.Subtitles[edition].Any())
                        {
                            int firstMediaIdx = info.Subtitles[edition].Keys.First();
                            for (int i = 0; i < info.Subtitles[edition][firstMediaIdx].Count; i++)
                            {
                                WebSubtitleStream webSubtitleStream = new WebSubtitleStream();
                                webSubtitleStream.Filename = info.Subtitles[edition][firstMediaIdx][i].IsEmbedded ? "Embedded" : info.Subtitles[edition][firstMediaIdx][i].SourcePath;
                                webSubtitleStream.ID       = info.Subtitles[edition][firstMediaIdx][i].StreamIndex + editionOffset;
                                webSubtitleStream.Index    = i;
                                if (info.Subtitles[edition][firstMediaIdx][i].Language != null)
                                {
                                    string language = info.Subtitles[edition][firstMediaIdx][i].Language == string.Empty ? UNDEFINED : info.Subtitles[edition][firstMediaIdx][i].Language;
                                    webSubtitleStream.Language     = language;
                                    webSubtitleStream.LanguageFull = language;
                                    if (language != UNDEFINED)
                                    {
                                        webSubtitleStream.LanguageFull = new CultureInfo(language).EnglishName;
                                    }
                                }
                                webSubtitleStreams.Add(webSubtitleStream);
                            }
                        }
                    }
                }
            }
            else if (item.Aspects.ContainsKey(AudioAspect.ASPECT_ID))
            {
                int edition = info.Metadata.Min(d => d.Key);
                if (info.IsAudio(edition))
                {
                    var audioAspect = item.GetAspect(AudioAspect.Metadata);
                    duration  = (long)audioAspect[AudioAspect.ATTR_DURATION];
                    container = info.Metadata[edition].AudioContainerType.ToString();
                }
            }
            else if (item.Aspects.ContainsKey(ImageAspect.ASPECT_ID))
            {
                int edition = info.Metadata.Min(d => d.Key);
                if (info.IsImage(edition))
                {
                    container = info.Metadata[edition].ImageContainerType.ToString();
                }
            }

            WebMediaInfo webMediaInfo = new WebMediaInfo
            {
                Duration        = duration * 1000,
                Container       = container,
                VideoStreams    = webVideoStreams,
                AudioStreams    = webAudioStreams,
                SubtitleStreams = webSubtitleStreams
            };

            return(webMediaInfo);
        }