diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..4922421b78 --- /dev/null +++ b/.gitignore @@ -0,0 +1,230 @@ +################# +## Eclipse +################# + +*.pydevproject +.project +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + +################# +## Media Browser +################# +ProgramData*/ +ProgramData-Server*/ +ProgramData-UI*/ + +################# +## Visual Studio +################# + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc +*.scc +*.psess +*.vsp +*.vspx +*.orig +*.rej +*.sdf +*.opensdf +*.ipch + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml +*.pubxml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +############# +## Windows detritus +############# + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store + + +############# +## Python +############# + +*.py[co] + +# Packages +*.egg +*.egg-info +dist/ +build/ +eggs/ +parts/ +var/ +sdist/ +develop-eggs/ +.installed.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +*.mo + +#Mr Developer +.mr.developer.cfg diff --git a/.hgignore b/.hgignore index c8162e4c87..c5c5c48773 100644 --- a/.hgignore +++ b/.hgignore @@ -1,43 +1,49 @@ -# use glob syntax -syntax: glob - -*.obj -*.pdb -*.user -*.aps -*.pch -*.vspscc -*.vssscc -*_i.c -*_p.c -*.ncb -*.suo -*.tlb -*.tlh -*.bak -*.cache -*.ilk -*.log -*.lib -*.sbr -*.scc -*.psess -*.vsp -*.orig -[Bb]in -[Dd]ebug*/ -obj/ -[Rr]elease*/ -ProgramData*/ -ProgramData-Server*/ -ProgramData-UI*/ -_ReSharper*/ -[Tt]humbs.db -[Tt]est[Rr]esult* -[Bb]uild[Ll]og.* -*.[Pp]ublish.xml -*.resharper - -# ncrunch files -*.ncrunchsolution -*.ncrunchproject +# use glob syntax +syntax: glob + +*.obj +*.pdb +*.user +*.aps +*.pch +*.vspscc +*.vssscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.tlh +*.bak +*.cache +*.ilk +*.log +*.lib +*.sbr +*.scc +*.psess +*.vsp +*.vspx +*.orig +*.rej +*.sdf +*.opensdf +*.ipch +[Bb]in +[Dd]ebug*/ +obj/ +[Rr]elease*/ +ProgramData*/ +ProgramData-Server*/ +ProgramData-UI*/ +_ReSharper*/ +[Tt]humbs.db +[Tt]est[Rr]esult* +[Bb]uild[Ll]og.* +*.[Pp]ublish.xml +*.resharper + +# ncrunch files +*.ncrunchsolution +*.ncrunchproject +Setup/* diff --git a/.hgtags b/.hgtags new file mode 100644 index 0000000000..4972205707 --- /dev/null +++ b/.hgtags @@ -0,0 +1,5 @@ +811bcaa9490681194bd72a01c4b0f91d78a0ec97 CO Version 0.12.12.25 +811bcaa9490681194bd72a01c4b0f91d78a0ec97 CO Version 0.12.12.25 +0000000000000000000000000000000000000000 CO Version 0.12.12.25 +0000000000000000000000000000000000000000 CO Version 0.12.12.25 +e98137e38224a0d82cd7d98a71264e0cddc91ca4 CO Version 0.12.12.25 diff --git a/BDInfo/BDInfo.csproj b/BDInfo/BDInfo.csproj new file mode 100644 index 0000000000..4e57d9e08d --- /dev/null +++ b/BDInfo/BDInfo.csproj @@ -0,0 +1,74 @@ + + + + + Debug + AnyCPU + {07B509C0-0C28-4F3F-8963-5263281F7E3D} + Library + Properties + BDInfo + BDInfo + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BDInfo/BDInfoSettings.cs b/BDInfo/BDInfoSettings.cs new file mode 100644 index 0000000000..108e03af1f --- /dev/null +++ b/BDInfo/BDInfoSettings.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BDInfo +{ + class BDInfoSettings + { + public static bool GenerateStreamDiagnostics + { + get + { + return true; + } + } + + public static bool EnableSSIF + { + get + { + return true; + } + } + + public static bool AutosaveReport + { + get + { + return false; + } + } + + public static bool GenerateFrameDataFile + { + get + { + return false; + } + } + + public static bool FilterLoopingPlaylists + { + get + { + return true; + } + } + + public static bool FilterShortPlaylists + { + get + { + return false; + } + } + + public static int FilterShortPlaylistsValue + { + get + { + return 0; + } + } + + public static bool UseImagePrefix + { + get + { + return false; + } + } + + public static string UseImagePrefixValue + { + get + { + return null; + } + } + + /// + /// Setting this to false throws an IComparer error on some discs. + /// + public static bool KeepStreamOrder + { + get + { + return true; + } + } + + public static bool GenerateTextSummary + { + get + { + return false; + } + } + + public static string LastPath + { + get + { + return string.Empty; + } + } + } +} diff --git a/BDInfo/BDROM.cs b/BDInfo/BDROM.cs new file mode 100644 index 0000000000..299e7aad70 --- /dev/null +++ b/BDInfo/BDROM.cs @@ -0,0 +1,482 @@ +//============================================================================ +// BDInfo - Blu-ray Video and Audio Analysis Tool +// Copyright © 2010 Cinema Squid +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//============================================================================= + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace BDInfo +{ + public class BDROM + { + public DirectoryInfo DirectoryRoot = null; + public DirectoryInfo DirectoryBDMV = null; + public DirectoryInfo DirectoryBDJO = null; + public DirectoryInfo DirectoryCLIPINF = null; + public DirectoryInfo DirectoryPLAYLIST = null; + public DirectoryInfo DirectorySNP = null; + public DirectoryInfo DirectorySSIF = null; + public DirectoryInfo DirectorySTREAM = null; + + public string VolumeLabel = null; + public ulong Size = 0; + public bool IsBDPlus = false; + public bool IsBDJava = false; + public bool IsDBOX = false; + public bool IsPSP = false; + public bool Is3D = false; + public bool Is50Hz = false; + + public Dictionary PlaylistFiles = + new Dictionary(); + public Dictionary StreamClipFiles = + new Dictionary(); + public Dictionary StreamFiles = + new Dictionary(); + public Dictionary InterleavedFiles = + new Dictionary(); + + private static List ExcludeDirs = new List { "ANY!", "AACS", "BDSVM", "ANYVM", "SLYVM" }; + + public delegate bool OnStreamClipFileScanError( + TSStreamClipFile streamClipFile, Exception ex); + + public event OnStreamClipFileScanError StreamClipFileScanError; + + public delegate bool OnStreamFileScanError( + TSStreamFile streamClipFile, Exception ex); + + public event OnStreamFileScanError StreamFileScanError; + + public delegate bool OnPlaylistFileScanError( + TSPlaylistFile playlistFile, Exception ex); + + public event OnPlaylistFileScanError PlaylistFileScanError; + + public BDROM( + string path) + { + // + // Locate BDMV directories. + // + + DirectoryBDMV = + GetDirectoryBDMV(path); + + if (DirectoryBDMV == null) + { + throw new Exception("Unable to locate BD structure."); + } + + DirectoryRoot = + DirectoryBDMV.Parent; + DirectoryBDJO = + GetDirectory("BDJO", DirectoryBDMV, 0); + DirectoryCLIPINF = + GetDirectory("CLIPINF", DirectoryBDMV, 0); + DirectoryPLAYLIST = + GetDirectory("PLAYLIST", DirectoryBDMV, 0); + DirectorySNP = + GetDirectory("SNP", DirectoryRoot, 0); + DirectorySTREAM = + GetDirectory("STREAM", DirectoryBDMV, 0); + DirectorySSIF = + GetDirectory("SSIF", DirectorySTREAM, 0); + + if (DirectoryCLIPINF == null + || DirectoryPLAYLIST == null) + { + throw new Exception("Unable to locate BD structure."); + } + + // + // Initialize basic disc properties. + // + + VolumeLabel = GetVolumeLabel(DirectoryRoot); + Size = (ulong)GetDirectorySize(DirectoryRoot); + + if (null != GetDirectory("BDSVM", DirectoryRoot, 0)) + { + IsBDPlus = true; + } + if (null != GetDirectory("SLYVM", DirectoryRoot, 0)) + { + IsBDPlus = true; + } + if (null != GetDirectory("ANYVM", DirectoryRoot, 0)) + { + IsBDPlus = true; + } + + if (DirectoryBDJO != null && + DirectoryBDJO.GetFiles().Length > 0) + { + IsBDJava = true; + } + + if (DirectorySNP != null && + (DirectorySNP.GetFiles("*.mnv").Length > 0 || DirectorySNP.GetFiles("*.MNV").Length > 0)) + { + IsPSP = true; + } + + if (DirectorySSIF != null && + DirectorySSIF.GetFiles().Length > 0) + { + Is3D = true; + } + + if (File.Exists(Path.Combine(DirectoryRoot.FullName, "FilmIndex.xml"))) + { + IsDBOX = true; + } + + // + // Initialize file lists. + // + + if (DirectoryPLAYLIST != null) + { + FileInfo[] files = DirectoryPLAYLIST.GetFiles("*.mpls"); + if (files.Length == 0) + { + files = DirectoryPLAYLIST.GetFiles("*.MPLS"); + } + foreach (FileInfo file in files) + { + PlaylistFiles.Add( + file.Name.ToUpper(), new TSPlaylistFile(this, file)); + } + } + + if (DirectorySTREAM != null) + { + FileInfo[] files = DirectorySTREAM.GetFiles("*.m2ts"); + if (files.Length == 0) + { + files = DirectoryPLAYLIST.GetFiles("*.M2TS"); + } + foreach (FileInfo file in files) + { + StreamFiles.Add( + file.Name.ToUpper(), new TSStreamFile(file)); + } + } + + if (DirectoryCLIPINF != null) + { + FileInfo[] files = DirectoryCLIPINF.GetFiles("*.clpi"); + if (files.Length == 0) + { + files = DirectoryPLAYLIST.GetFiles("*.CLPI"); + } + foreach (FileInfo file in files) + { + StreamClipFiles.Add( + file.Name.ToUpper(), new TSStreamClipFile(file)); + } + } + + if (DirectorySSIF != null) + { + FileInfo[] files = DirectorySSIF.GetFiles("*.ssif"); + if (files.Length == 0) + { + files = DirectorySSIF.GetFiles("*.SSIF"); + } + foreach (FileInfo file in files) + { + InterleavedFiles.Add( + file.Name.ToUpper(), new TSInterleavedFile(file)); + } + } + } + + public void Scan() + { + List errorStreamClipFiles = new List(); + foreach (TSStreamClipFile streamClipFile in StreamClipFiles.Values) + { + try + { + streamClipFile.Scan(); + } + catch (Exception ex) + { + errorStreamClipFiles.Add(streamClipFile); + if (StreamClipFileScanError != null) + { + if (StreamClipFileScanError(streamClipFile, ex)) + { + continue; + } + else + { + break; + } + } + else throw ex; + } + } + + foreach (TSStreamFile streamFile in StreamFiles.Values) + { + string ssifName = Path.GetFileNameWithoutExtension(streamFile.Name) + ".SSIF"; + if (InterleavedFiles.ContainsKey(ssifName)) + { + streamFile.InterleavedFile = InterleavedFiles[ssifName]; + } + } + + TSStreamFile[] streamFiles = new TSStreamFile[StreamFiles.Count]; + StreamFiles.Values.CopyTo(streamFiles, 0); + Array.Sort(streamFiles, CompareStreamFiles); + + List errorPlaylistFiles = new List(); + foreach (TSPlaylistFile playlistFile in PlaylistFiles.Values) + { + try + { + playlistFile.Scan(StreamFiles, StreamClipFiles); + } + catch (Exception ex) + { + errorPlaylistFiles.Add(playlistFile); + if (PlaylistFileScanError != null) + { + if (PlaylistFileScanError(playlistFile, ex)) + { + continue; + } + else + { + break; + } + } + else throw ex; + } + } + + List errorStreamFiles = new List(); + foreach (TSStreamFile streamFile in streamFiles) + { + try + { + List playlists = new List(); + foreach (TSPlaylistFile playlist in PlaylistFiles.Values) + { + foreach (TSStreamClip streamClip in playlist.StreamClips) + { + if (streamClip.Name == streamFile.Name) + { + playlists.Add(playlist); + break; + } + } + } + streamFile.Scan(playlists, false); + } + catch (Exception ex) + { + errorStreamFiles.Add(streamFile); + if (StreamFileScanError != null) + { + if (StreamFileScanError(streamFile, ex)) + { + continue; + } + else + { + break; + } + } + else throw ex; + } + } + + foreach (TSPlaylistFile playlistFile in PlaylistFiles.Values) + { + playlistFile.Initialize(); + if (!Is50Hz) + { + foreach (TSVideoStream videoStream in playlistFile.VideoStreams) + { + if (videoStream.FrameRate == TSFrameRate.FRAMERATE_25 || + videoStream.FrameRate == TSFrameRate.FRAMERATE_50) + { + Is50Hz = true; + } + } + } + } + } + + private DirectoryInfo GetDirectoryBDMV( + string path) + { + DirectoryInfo dir = new DirectoryInfo(path); + + while (dir != null) + { + if (dir.Name == "BDMV") + { + return dir; + } + dir = dir.Parent; + } + + return GetDirectory("BDMV", new DirectoryInfo(path), 0); + } + + private DirectoryInfo GetDirectory( + string name, + DirectoryInfo dir, + int searchDepth) + { + if (dir != null) + { + DirectoryInfo[] children = dir.GetDirectories(); + foreach (DirectoryInfo child in children) + { + if (child.Name == name) + { + return child; + } + } + if (searchDepth > 0) + { + foreach (DirectoryInfo child in children) + { + GetDirectory( + name, child, searchDepth - 1); + } + } + } + return null; + } + + private long GetDirectorySize(DirectoryInfo directoryInfo) + { + long size = 0; + + //if (!ExcludeDirs.Contains(directoryInfo.Name.ToUpper())) // TODO: Keep? + { + FileInfo[] pathFiles = directoryInfo.GetFiles(); + foreach (FileInfo pathFile in pathFiles) + { + if (pathFile.Extension.ToUpper() == ".SSIF") + { + continue; + } + size += pathFile.Length; + } + + DirectoryInfo[] pathChildren = directoryInfo.GetDirectories(); + foreach (DirectoryInfo pathChild in pathChildren) + { + size += GetDirectorySize(pathChild); + } + } + + return size; + } + + private string GetVolumeLabel(DirectoryInfo dir) + { + uint serialNumber = 0; + uint maxLength = 0; + uint volumeFlags = new uint(); + StringBuilder volumeLabel = new StringBuilder(256); + StringBuilder fileSystemName = new StringBuilder(256); + string label = ""; + + try + { + long result = GetVolumeInformation( + dir.Name, + volumeLabel, + (uint)volumeLabel.Capacity, + ref serialNumber, + ref maxLength, + ref volumeFlags, + fileSystemName, + (uint)fileSystemName.Capacity); + + label = volumeLabel.ToString(); + } + catch { } + + if (label.Length == 0) + { + label = dir.Name; + } + + return label; + } + + public static int CompareStreamFiles( + TSStreamFile x, + TSStreamFile y) + { + // TODO: Use interleaved file sizes + + if ((x == null || x.FileInfo == null) && (y == null || y.FileInfo == null)) + { + return 0; + } + else if ((x == null || x.FileInfo == null) && (y != null && y.FileInfo != null)) + { + return 1; + } + else if ((x != null || x.FileInfo != null) && (y == null || y.FileInfo == null)) + { + return -1; + } + else + { + if (x.FileInfo.Length > y.FileInfo.Length) + { + return 1; + } + else if (y.FileInfo.Length > x.FileInfo.Length) + { + return -1; + } + else + { + return 0; + } + } + } + + [DllImport("kernel32.dll")] + private static extern long GetVolumeInformation( + string PathName, + StringBuilder VolumeNameBuffer, + uint VolumeNameSize, + ref uint VolumeSerialNumber, + ref uint MaximumComponentLength, + ref uint FileSystemFlags, + StringBuilder FileSystemNameBuffer, + uint FileSystemNameSize); + } +} diff --git a/BDInfo/LanguageCodes.cs b/BDInfo/LanguageCodes.cs new file mode 100644 index 0000000000..c4f4dc4a6a --- /dev/null +++ b/BDInfo/LanguageCodes.cs @@ -0,0 +1,496 @@ +//============================================================================ +// BDInfo - Blu-ray Video and Audio Analysis Tool +// Copyright © 2010 Cinema Squid +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//============================================================================= + +using System; +using System.Collections.Generic; +using System.Text; + +namespace BDInfo +{ + public abstract class LanguageCodes + { + public static string GetName(string code) + { + switch (code) + { + case "abk": return "Abkhazian"; + case "ace": return "Achinese"; + case "ach": return "Acoli"; + case "ada": return "Adangme"; + case "aar": return "Afar"; + case "afh": return "Afrihili"; + case "afr": return "Afrikaans"; + case "afa": return "Afro-Asiatic (Other)"; + case "aka": return "Akan"; + case "akk": return "Akkadian"; + case "alb": return "Albanian"; + case "sqi": return "Albanian"; + case "ale": return "Aleut"; + case "alg": return "Algonquian languages"; + case "tut": return "Altaic (Other)"; + case "amh": return "Amharic"; + case "apa": return "Apache languages"; + case "ara": return "Arabic"; + case "arc": return "Aramaic"; + case "arp": return "Arapaho"; + case "arn": return "Araucanian"; + case "arw": return "Arawak"; + case "arm": return "Armenian"; + case "hye": return "Armenian"; + case "art": return "Artificial (Other)"; + case "asm": return "Assamese"; + case "ath": return "Athapascan languages"; + case "aus": return "Australian languages"; + case "map": return "Austronesian (Other)"; + case "ava": return "Avaric"; + case "ave": return "Avestan"; + case "awa": return "Awadhi"; + case "aym": return "Aymara"; + case "aze": return "Azerbaijani"; + case "ban": return "Balinese"; + case "bat": return "Baltic (Other)"; + case "bal": return "Baluchi"; + case "bam": return "Bambara"; + case "bai": return "Bamileke languages"; + case "bad": return "Banda"; + case "bnt": return "Bantu (Other)"; + case "bas": return "Basa"; + case "bak": return "Bashkir"; + case "baq": return "Basque"; + case "eus": return "Basque"; + case "btk": return "Batak (Indonesia)"; + case "bej": return "Beja"; + case "bel": return "Belarusian"; + case "bem": return "Bemba"; + case "ben": return "Bengali"; + case "ber": return "Berber (Other)"; + case "bho": return "Bhojpuri"; + case "bih": return "Bihari"; + case "bik": return "Bikol"; + case "bin": return "Bini"; + case "bis": return "Bislama"; + case "bos": return "Bosnian"; + case "bra": return "Braj"; + case "bre": return "Breton"; + case "bug": return "Buginese"; + case "bul": return "Bulgarian"; + case "bua": return "Buriat"; + case "bur": return "Burmese"; + case "mya": return "Burmese"; + case "cad": return "Caddo"; + case "car": return "Carib"; + case "cat": return "Catalan"; + case "cau": return "Caucasian (Other)"; + case "ceb": return "Cebuano"; + case "cel": return "Celtic (Other)"; + case "cai": return "Central American Indian (Other)"; + case "chg": return "Chagatai"; + case "cmc": return "Chamic languages"; + case "cha": return "Chamorro"; + case "che": return "Chechen"; + case "chr": return "Cherokee"; + case "chy": return "Cheyenne"; + case "chb": return "Chibcha"; + case "chi": return "Chinese"; + case "zho": return "Chinese"; + case "chn": return "Chinook jargon"; + case "chp": return "Chipewyan"; + case "cho": return "Choctaw"; + case "chu": return "Church Slavic"; + case "chk": return "Chuukese"; + case "chv": return "Chuvash"; + case "cop": return "Coptic"; + case "cor": return "Cornish"; + case "cos": return "Corsican"; + case "cre": return "Cree"; + case "mus": return "Creek"; + case "crp": return "Creoles and pidgins (Other)"; + case "cpe": return "Creoles and pidgins,"; + case "cpf": return "Creoles and pidgins,"; + case "cpp": return "Creoles and pidgins,"; + case "scr": return "Croatian"; + case "hrv": return "Croatian"; + case "cus": return "Cushitic (Other)"; + case "cze": return "Czech"; + case "ces": return "Czech"; + case "dak": return "Dakota"; + case "dan": return "Danish"; + case "day": return "Dayak"; + case "del": return "Delaware"; + case "din": return "Dinka"; + case "div": return "Divehi"; + case "doi": return "Dogri"; + case "dgr": return "Dogrib"; + case "dra": return "Dravidian (Other)"; + case "dua": return "Duala"; + case "dut": return "Dutch"; + case "nld": return "Dutch"; + case "dum": return "Dutch, Middle (ca. 1050-1350)"; + case "dyu": return "Dyula"; + case "dzo": return "Dzongkha"; + case "efi": return "Efik"; + case "egy": return "Egyptian (Ancient)"; + case "eka": return "Ekajuk"; + case "elx": return "Elamite"; + case "eng": return "English"; + case "enm": return "English, Middle (1100-1500)"; + case "ang": return "English, Old (ca.450-1100)"; + case "epo": return "Esperanto"; + case "est": return "Estonian"; + case "ewe": return "Ewe"; + case "ewo": return "Ewondo"; + case "fan": return "Fang"; + case "fat": return "Fanti"; + case "fao": return "Faroese"; + case "fij": return "Fijian"; + case "fin": return "Finnish"; + case "fiu": return "Finno-Ugrian (Other)"; + case "fon": return "Fon"; + case "fre": return "French"; + case "fra": return "French"; + case "frm": return "French, Middle (ca.1400-1600)"; + case "fro": return "French, Old (842-ca.1400)"; + case "fry": return "Frisian"; + case "fur": return "Friulian"; + case "ful": return "Fulah"; + case "gaa": return "Ga"; + case "glg": return "Gallegan"; + case "lug": return "Ganda"; + case "gay": return "Gayo"; + case "gba": return "Gbaya"; + case "gez": return "Geez"; + case "geo": return "Georgian"; + case "kat": return "Georgian"; + case "ger": return "German"; + case "deu": return "German"; + case "nds": return "Saxon"; + case "gmh": return "German, Middle High (ca.1050-1500)"; + case "goh": return "German, Old High (ca.750-1050)"; + case "gem": return "Germanic (Other)"; + case "gil": return "Gilbertese"; + case "gon": return "Gondi"; + case "gor": return "Gorontalo"; + case "got": return "Gothic"; + case "grb": return "Grebo"; + case "grc": return "Greek, Ancient (to 1453)"; + case "gre": return "Greek"; + case "ell": return "Greek"; + case "grn": return "Guarani"; + case "guj": return "Gujarati"; + case "gwi": return "Gwich´in"; + case "hai": return "Haida"; + case "hau": return "Hausa"; + case "haw": return "Hawaiian"; + case "heb": return "Hebrew"; + case "her": return "Herero"; + case "hil": return "Hiligaynon"; + case "him": return "Himachali"; + case "hin": return "Hindi"; + case "hmo": return "Hiri Motu"; + case "hit": return "Hittite"; + case "hmn": return "Hmong"; + case "hun": return "Hungarian"; + case "hup": return "Hupa"; + case "iba": return "Iban"; + case "ice": return "Icelandic"; + case "isl": return "Icelandic"; + case "ibo": return "Igbo"; + case "ijo": return "Ijo"; + case "ilo": return "Iloko"; + case "inc": return "Indic (Other)"; + case "ine": return "Indo-European (Other)"; + case "ind": return "Indonesian"; + case "ina": return "Interlingua (International"; + case "ile": return "Interlingue"; + case "iku": return "Inuktitut"; + case "ipk": return "Inupiaq"; + case "ira": return "Iranian (Other)"; + case "gle": return "Irish"; + case "mga": return "Irish, Middle (900-1200)"; + case "sga": return "Irish, Old (to 900)"; + case "iro": return "Iroquoian languages"; + case "ita": return "Italian"; + case "jpn": return "Japanese"; + case "jav": return "Javanese"; + case "jrb": return "Judeo-Arabic"; + case "jpr": return "Judeo-Persian"; + case "kab": return "Kabyle"; + case "kac": return "Kachin"; + case "kal": return "Kalaallisut"; + case "kam": return "Kamba"; + case "kan": return "Kannada"; + case "kau": return "Kanuri"; + case "kaa": return "Kara-Kalpak"; + case "kar": return "Karen"; + case "kas": return "Kashmiri"; + case "kaw": return "Kawi"; + case "kaz": return "Kazakh"; + case "kha": return "Khasi"; + case "khm": return "Khmer"; + case "khi": return "Khoisan (Other)"; + case "kho": return "Khotanese"; + case "kik": return "Kikuyu"; + case "kmb": return "Kimbundu"; + case "kin": return "Kinyarwanda"; + case "kir": return "Kirghiz"; + case "kom": return "Komi"; + case "kon": return "Kongo"; + case "kok": return "Konkani"; + case "kor": return "Korean"; + case "kos": return "Kosraean"; + case "kpe": return "Kpelle"; + case "kro": return "Kru"; + case "kua": return "Kuanyama"; + case "kum": return "Kumyk"; + case "kur": return "Kurdish"; + case "kru": return "Kurukh"; + case "kut": return "Kutenai"; + case "lad": return "Ladino"; + case "lah": return "Lahnda"; + case "lam": return "Lamba"; + case "lao": return "Lao"; + case "lat": return "Latin"; + case "lav": return "Latvian"; + case "ltz": return "Letzeburgesch"; + case "lez": return "Lezghian"; + case "lin": return "Lingala"; + case "lit": return "Lithuanian"; + case "loz": return "Lozi"; + case "lub": return "Luba-Katanga"; + case "lua": return "Luba-Lulua"; + case "lui": return "Luiseno"; + case "lun": return "Lunda"; + case "luo": return "Luo (Kenya and Tanzania)"; + case "lus": return "Lushai"; + case "mac": return "Macedonian"; + case "mkd": return "Macedonian"; + case "mad": return "Madurese"; + case "mag": return "Magahi"; + case "mai": return "Maithili"; + case "mak": return "Makasar"; + case "mlg": return "Malagasy"; + case "may": return "Malay"; + case "msa": return "Malay"; + case "mal": return "Malayalam"; + case "mlt": return "Maltese"; + case "mnc": return "Manchu"; + case "mdr": return "Mandar"; + case "man": return "Mandingo"; + case "mni": return "Manipuri"; + case "mno": return "Manobo languages"; + case "glv": return "Manx"; + case "mao": return "Maori"; + case "mri": return "Maori"; + case "mar": return "Marathi"; + case "chm": return "Mari"; + case "mah": return "Marshall"; + case "mwr": return "Marwari"; + case "mas": return "Masai"; + case "myn": return "Mayan languages"; + case "men": return "Mende"; + case "mic": return "Micmac"; + case "min": return "Minangkabau"; + case "mis": return "Miscellaneous languages"; + case "moh": return "Mohawk"; + case "mol": return "Moldavian"; + case "mkh": return "Mon-Khmer (Other)"; + case "lol": return "Mongo"; + case "mon": return "Mongolian"; + case "mos": return "Mossi"; + case "mul": return "Multiple languages"; + case "mun": return "Munda languages"; + case "nah": return "Nahuatl"; + case "nau": return "Nauru"; + case "nav": return "Navajo"; + case "nde": return "Ndebele, North"; + case "nbl": return "Ndebele, South"; + case "ndo": return "Ndonga"; + case "nep": return "Nepali"; + case "new": return "Newari"; + case "nia": return "Nias"; + case "nic": return "Niger-Kordofanian (Other)"; + case "ssa": return "Nilo-Saharan (Other)"; + case "niu": return "Niuean"; + case "non": return "Norse, Old"; + case "nai": return "North American Indian (Other)"; + case "sme": return "Northern Sami"; + case "nor": return "Norwegian"; + case "nob": return "Norwegian Bokmål"; + case "nno": return "Norwegian Nynorsk"; + case "nub": return "Nubian languages"; + case "nym": return "Nyamwezi"; + case "nya": return "Nyanja"; + case "nyn": return "Nyankole"; + case "nyo": return "Nyoro"; + case "nzi": return "Nzima"; + case "oci": return "Occitan"; + case "oji": return "Ojibwa"; + case "ori": return "Oriya"; + case "orm": return "Oromo"; + case "osa": return "Osage"; + case "oss": return "Ossetian"; + case "oto": return "Otomian languages"; + case "pal": return "Pahlavi"; + case "pau": return "Palauan"; + case "pli": return "Pali"; + case "pam": return "Pampanga"; + case "pag": return "Pangasinan"; + case "pan": return "Panjabi"; + case "pap": return "Papiamento"; + case "paa": return "Papuan (Other)"; + case "per": return "Persian"; + case "fas": return "Persian"; + case "peo": return "Persian, Old (ca.600-400 B.C.)"; + case "phi": return "Philippine (Other)"; + case "phn": return "Phoenician"; + case "pon": return "Pohnpeian"; + case "pol": return "Polish"; + case "por": return "Portuguese"; + case "pra": return "Prakrit languages"; + case "pro": return "Provençal"; + case "pus": return "Pushto"; + case "que": return "Quechua"; + case "roh": return "Raeto-Romance"; + case "raj": return "Rajasthani"; + case "rap": return "Rapanui"; + case "rar": return "Rarotongan"; + case "roa": return "Romance (Other)"; + case "rum": return "Romanian"; + case "ron": return "Romanian"; + case "rom": return "Romany"; + case "run": return "Rundi"; + case "rus": return "Russian"; + case "sal": return "Salishan languages"; + case "sam": return "Samaritan Aramaic"; + case "smi": return "Sami languages (Other)"; + case "smo": return "Samoan"; + case "sad": return "Sandawe"; + case "sag": return "Sango"; + case "san": return "Sanskrit"; + case "sat": return "Santali"; + case "srd": return "Sardinian"; + case "sas": return "Sasak"; + case "sco": return "Scots"; + case "gla": return "Gaelic"; + case "sel": return "Selkup"; + case "sem": return "Semitic (Other)"; + case "scc": return "Serbian"; + case "srp": return "Serbian"; + case "srr": return "Serer"; + case "shn": return "Shan"; + case "sna": return "Shona"; + case "sid": return "Sidamo"; + case "sgn": return "Sign languages"; + case "bla": return "Siksika"; + case "snd": return "Sindhi"; + case "sin": return "Sinhalese"; + case "sit": return "Sino-Tibetan (Other)"; + case "sio": return "Siouan languages"; + case "den": return "Slave (Athapascan)"; + case "sla": return "Slavic (Other)"; + case "slo": return "Slovak"; + case "slk": return "Slovak"; + case "slv": return "Slovenian"; + case "sog": return "Sogdian"; + case "som": return "Somali"; + case "son": return "Songhai"; + case "snk": return "Soninke"; + case "wen": return "Sorbian languages"; + case "nso": return "Sotho, Northern"; + case "sot": return "Sotho, Southern"; + case "sai": return "South American Indian (Other)"; + case "spa": return "Spanish"; + case "suk": return "Sukuma"; + case "sux": return "Sumerian"; + case "sun": return "Sundanese"; + case "sus": return "Susu"; + case "swa": return "Swahili"; + case "ssw": return "Swati"; + case "swe": return "Swedish"; + case "syr": return "Syriac"; + case "tgl": return "Tagalog"; + case "tah": return "Tahitian"; + case "tai": return "Tai (Other)"; + case "tgk": return "Tajik"; + case "tmh": return "Tamashek"; + case "tam": return "Tamil"; + case "tat": return "Tatar"; + case "tel": return "Telugu"; + case "ter": return "Tereno"; + case "tet": return "Tetum"; + case "tha": return "Thai"; + case "tib": return "Tibetan"; + case "bod": return "Tibetan"; + case "tig": return "Tigre"; + case "tir": return "Tigrinya"; + case "tem": return "Timne"; + case "tiv": return "Tiv"; + case "tli": return "Tlingit"; + case "tpi": return "Tok Pisin"; + case "tkl": return "Tokelau"; + case "tog": return "Tonga (Nyasa)"; + case "ton": return "Tonga (Tonga Islands)"; + case "tsi": return "Tsimshian"; + case "tso": return "Tsonga"; + case "tsn": return "Tswana"; + case "tum": return "Tumbuka"; + case "tur": return "Turkish"; + case "ota": return "Turkish, Ottoman (1500-1928)"; + case "tuk": return "Turkmen"; + case "tvl": return "Tuvalu"; + case "tyv": return "Tuvinian"; + case "twi": return "Twi"; + case "uga": return "Ugaritic"; + case "uig": return "Uighur"; + case "ukr": return "Ukrainian"; + case "umb": return "Umbundu"; + case "und": return "Undetermined"; + case "urd": return "Urdu"; + case "uzb": return "Uzbek"; + case "vai": return "Vai"; + case "ven": return "Venda"; + case "vie": return "Vietnamese"; + case "vol": return "Volapük"; + case "vot": return "Votic"; + case "wak": return "Wakashan languages"; + case "wal": return "Walamo"; + case "war": return "Waray"; + case "was": return "Washo"; + case "wel": return "Welsh"; + case "cym": return "Welsh"; + case "wol": return "Wolof"; + case "xho": return "Xhosa"; + case "sah": return "Yakut"; + case "yao": return "Yao"; + case "yap": return "Yapese"; + case "yid": return "Yiddish"; + case "yor": return "Yoruba"; + case "ypk": return "Yupik languages"; + case "znd": return "Zande"; + case "zap": return "Zapotec"; + case "zen": return "Zenaga"; + case "zha": return "Zhuang"; + case "zul": return "Zulu"; + case "zun": return "Zuni"; + + default: return code; + } + } + } +} diff --git a/BDInfo/Properties/AssemblyInfo.cs b/BDInfo/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..9ea6496621 --- /dev/null +++ b/BDInfo/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("BDInfo")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("BDInfo")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2ecb9fe5-e2da-4b49-880b-d9887a84c311")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/BDInfo/ReadMe.txt b/BDInfo/ReadMe.txt new file mode 100644 index 0000000000..68326d560a --- /dev/null +++ b/BDInfo/ReadMe.txt @@ -0,0 +1,5 @@ +The source is taken from the BDRom folder of this project: + +http://www.cinemasquid.com/blu-ray/tools/bdinfo + +BDInfoSettings was taken from the FormSettings class, and changed so that the settings all return defaults. \ No newline at end of file diff --git a/BDInfo/TSCodecAC3.cs b/BDInfo/TSCodecAC3.cs new file mode 100644 index 0000000000..79552df507 --- /dev/null +++ b/BDInfo/TSCodecAC3.cs @@ -0,0 +1,312 @@ +//============================================================================ +// BDInfo - Blu-ray Video and Audio Analysis Tool +// Copyright © 2010 Cinema Squid +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//============================================================================= + +#undef DEBUG +using System; +using System.IO; +using System.Collections.Generic; +using System.Text; + +namespace BDInfo +{ + public abstract class TSCodecAC3 + { + private static byte[] eac3_blocks = new byte[] { 1, 2, 3, 6 }; + + public static void Scan( + TSAudioStream stream, + TSStreamBuffer buffer, + ref string tag) + { + if (stream.IsInitialized) return; + + byte[] sync = buffer.ReadBytes(2); + if (sync == null || + sync[0] != 0x0B || + sync[1] != 0x77) + { + return; + } + + int sr_code = 0; + int frame_size = 0; + int frame_size_code = 0; + int channel_mode = 0; + int lfe_on = 0; + int dial_norm = 0; + int num_blocks = 0; + + byte[] hdr = buffer.ReadBytes(4); + int bsid = (hdr[3] & 0xF8) >> 3; + buffer.Seek(-4, SeekOrigin.Current); + if (bsid <= 10) + { + byte[] crc = buffer.ReadBytes(2); + sr_code = buffer.ReadBits(2); + frame_size_code = buffer.ReadBits(6); + bsid = buffer.ReadBits(5); + int bsmod = buffer.ReadBits(3); + + channel_mode = buffer.ReadBits(3); + int cmixlev = 0; + if (((channel_mode & 0x1) > 0) && (channel_mode != 0x1)) + { + cmixlev = buffer.ReadBits(2); + } + int surmixlev = 0; + if ((channel_mode & 0x4) > 0) + { + surmixlev = buffer.ReadBits(2); + } + int dsurmod = 0; + if (channel_mode == 0x2) + { + dsurmod = buffer.ReadBits(2); + if (dsurmod == 0x2) + { + stream.AudioMode = TSAudioMode.Surround; + } + } + lfe_on = buffer.ReadBits(1); + dial_norm = buffer.ReadBits(5); + int compr = 0; + if (1 == buffer.ReadBits(1)) + { + compr = buffer.ReadBits(8); + } + int langcod = 0; + if (1 == buffer.ReadBits(1)) + { + langcod = buffer.ReadBits(8); + } + int mixlevel = 0; + int roomtyp = 0; + if (1 == buffer.ReadBits(1)) + { + mixlevel = buffer.ReadBits(5); + roomtyp = buffer.ReadBits(2); + } + if (channel_mode == 0) + { + int dialnorm2 = buffer.ReadBits(5); + int compr2 = 0; + if (1 == buffer.ReadBits(1)) + { + compr2 = buffer.ReadBits(8); + } + int langcod2 = 0; + if (1 == buffer.ReadBits(1)) + { + langcod2 = buffer.ReadBits(8); + } + int mixlevel2 = 0; + int roomtyp2 = 0; + if (1 == buffer.ReadBits(1)) + { + mixlevel2 = buffer.ReadBits(5); + roomtyp2 = buffer.ReadBits(2); + } + } + int copyrightb = buffer.ReadBits(1); + int origbs = buffer.ReadBits(1); + if (bsid == 6) + { + if (1 == buffer.ReadBits(1)) + { + int dmixmod = buffer.ReadBits(2); + int ltrtcmixlev = buffer.ReadBits(3); + int ltrtsurmixlev = buffer.ReadBits(3); + int lorocmixlev = buffer.ReadBits(3); + int lorosurmixlev = buffer.ReadBits(3); + } + if (1 == buffer.ReadBits(1)) + { + int dsurexmod = buffer.ReadBits(2); + int dheadphonmod = buffer.ReadBits(2); + if (dheadphonmod == 0x2) + { + // TODO + } + int adconvtyp = buffer.ReadBits(1); + int xbsi2 = buffer.ReadBits(8); + int encinfo = buffer.ReadBits(1); + if (dsurexmod == 2) + { + stream.AudioMode = TSAudioMode.Extended; + } + } + } + } + else + { + int frame_type = buffer.ReadBits(2); + int substreamid = buffer.ReadBits(3); + frame_size = (buffer.ReadBits(11) + 1) << 1; + + sr_code = buffer.ReadBits(2); + if (sr_code == 3) + { + sr_code = buffer.ReadBits(2); + } + else + { + num_blocks = buffer.ReadBits(2); + } + channel_mode = buffer.ReadBits(3); + lfe_on = buffer.ReadBits(1); + } + + switch (channel_mode) + { + case 0: // 1+1 + stream.ChannelCount = 2; + if (stream.AudioMode == TSAudioMode.Unknown) + { + stream.AudioMode = TSAudioMode.DualMono; + } + break; + case 1: // 1/0 + stream.ChannelCount = 1; + break; + case 2: // 2/0 + stream.ChannelCount = 2; + if (stream.AudioMode == TSAudioMode.Unknown) + { + stream.AudioMode = TSAudioMode.Stereo; + } + break; + case 3: // 3/0 + stream.ChannelCount = 3; + break; + case 4: // 2/1 + stream.ChannelCount = 3; + break; + case 5: // 3/1 + stream.ChannelCount = 4; + break; + case 6: // 2/2 + stream.ChannelCount = 4; + break; + case 7: // 3/2 + stream.ChannelCount = 5; + break; + default: + stream.ChannelCount = 0; + break; + } + + switch (sr_code) + { + case 0: + stream.SampleRate = 48000; + break; + case 1: + stream.SampleRate = 44100; + break; + case 2: + stream.SampleRate = 32000; + break; + default: + stream.SampleRate = 0; + break; + } + + if (bsid <= 10) + { + switch (frame_size_code >> 1) + { + case 18: + stream.BitRate = 640000; + break; + case 17: + stream.BitRate = 576000; + break; + case 16: + stream.BitRate = 512000; + break; + case 15: + stream.BitRate = 448000; + break; + case 14: + stream.BitRate = 384000; + break; + case 13: + stream.BitRate = 320000; + break; + case 12: + stream.BitRate = 256000; + break; + case 11: + stream.BitRate = 224000; + break; + case 10: + stream.BitRate = 192000; + break; + case 9: + stream.BitRate = 160000; + break; + case 8: + stream.BitRate = 128000; + break; + case 7: + stream.BitRate = 112000; + break; + case 6: + stream.BitRate = 96000; + break; + case 5: + stream.BitRate = 80000; + break; + case 4: + stream.BitRate = 64000; + break; + case 3: + stream.BitRate = 56000; + break; + case 2: + stream.BitRate = 48000; + break; + case 1: + stream.BitRate = 40000; + break; + case 0: + stream.BitRate = 32000; + break; + default: + stream.BitRate = 0; + break; + } + } + else + { + stream.BitRate = (long) + (4.0 * frame_size * stream.SampleRate / (num_blocks * 256)); + } + + stream.LFE = lfe_on; + if (stream.StreamType != TSStreamType.AC3_PLUS_AUDIO && + stream.StreamType != TSStreamType.AC3_PLUS_SECONDARY_AUDIO) + { + stream.DialNorm = dial_norm - 31; + } + stream.IsVBR = false; + stream.IsInitialized = true; + } + } +} diff --git a/BDInfo/TSCodecAVC.cs b/BDInfo/TSCodecAVC.cs new file mode 100644 index 0000000000..0b15f26f3e --- /dev/null +++ b/BDInfo/TSCodecAVC.cs @@ -0,0 +1,151 @@ +//============================================================================ +// BDInfo - Blu-ray Video and Audio Analysis Tool +// Copyright © 2010 Cinema Squid +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//============================================================================= + +using System; +using System.Collections.Generic; +using System.Text; + +namespace BDInfo +{ + public abstract class TSCodecAVC + { + public static void Scan( + TSVideoStream stream, + TSStreamBuffer buffer, + ref string tag) + { + uint parse = 0; + byte accessUnitDelimiterParse = 0; + byte sequenceParameterSetParse = 0; + string profile = null; + string level = null; + byte constraintSet0Flag = 0; + byte constraintSet1Flag = 0; + byte constraintSet2Flag = 0; + byte constraintSet3Flag = 0; + + for (int i = 0; i < buffer.Length; i++) + { + parse = (parse << 8) + buffer.ReadByte(); + + if (parse == 0x00000109) + { + accessUnitDelimiterParse = 1; + } + else if (accessUnitDelimiterParse > 0) + { + --accessUnitDelimiterParse; + if (accessUnitDelimiterParse == 0) + { + switch ((parse & 0xFF) >> 5) + { + case 0: // I + case 3: // SI + case 5: // I, SI + tag = "I"; + break; + + case 1: // I, P + case 4: // SI, SP + case 6: // I, SI, P, SP + tag = "P"; + break; + + case 2: // I, P, B + case 7: // I, SI, P, SP, B + tag = "B"; + break; + } + if (stream.IsInitialized) return; + } + } + else if (parse == 0x00000127 || parse == 0x00000167) + { + sequenceParameterSetParse = 3; + } + else if (sequenceParameterSetParse > 0) + { + --sequenceParameterSetParse; + switch (sequenceParameterSetParse) + { + case 2: + switch (parse & 0xFF) + { + case 66: + profile = "Baseline Profile"; + break; + case 77: + profile = "Main Profile"; + break; + case 88: + profile = "Extended Profile"; + break; + case 100: + profile = "High Profile"; + break; + case 110: + profile = "High 10 Profile"; + break; + case 122: + profile = "High 4:2:2 Profile"; + break; + case 144: + profile = "High 4:4:4 Profile"; + break; + default: + profile = "Unknown Profile"; + break; + } + break; + + case 1: + constraintSet0Flag = (byte) + ((parse & 0x80) >> 7); + constraintSet1Flag = (byte) + ((parse & 0x40) >> 6); + constraintSet2Flag = (byte) + ((parse & 0x20) >> 5); + constraintSet3Flag = (byte) + ((parse & 0x10) >> 4); + break; + + case 0: + byte b = (byte)(parse & 0xFF); + if (b == 11 && constraintSet3Flag == 1) + { + level = "1b"; + } + else + { + level = string.Format( + "{0:D}.{1:D}", + b / 10, (b - ((b / 10) * 10))); + } + stream.EncodingProfile = string.Format( + "{0} {1}", profile, level); + stream.IsVBR = true; + stream.IsInitialized = true; + break; + } + } + } + return; + } + } +} diff --git a/BDInfo/TSCodecDTS.cs b/BDInfo/TSCodecDTS.cs new file mode 100644 index 0000000000..90af48e934 --- /dev/null +++ b/BDInfo/TSCodecDTS.cs @@ -0,0 +1,162 @@ +//============================================================================ +// BDInfo - Blu-ray Video and Audio Analysis Tool +// Copyright © 2010 Cinema Squid +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//============================================================================= + +using System; +using System.Collections.Generic; +using System.Text; + +namespace BDInfo +{ + public abstract class TSCodecDTS + { + private static int[] dca_sample_rates = + { + 0, 8000, 16000, 32000, 0, 0, 11025, 22050, 44100, 0, 0, + 12000, 24000, 48000, 96000, 192000 + }; + + private static int[] dca_bit_rates = + { + 32000, 56000, 64000, 96000, 112000, 128000, + 192000, 224000, 256000, 320000, 384000, + 448000, 512000, 576000, 640000, 768000, + 896000, 1024000, 1152000, 1280000, 1344000, + 1408000, 1411200, 1472000, 1509000, 1920000, + 2048000, 3072000, 3840000, 1/*open*/, 2/*variable*/, 3/*lossless*/ + }; + + private static int[] dca_channels = + { + 1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7, 8, 8 + }; + + private static int[] dca_bits_per_sample = + { + 16, 16, 20, 20, 0, 24, 24 + }; + + public static void Scan( + TSAudioStream stream, + TSStreamBuffer buffer, + long bitrate, + ref string tag) + { + if (stream.IsInitialized) return; + + bool syncFound = false; + uint sync = 0; + for (int i = 0; i < buffer.Length; i++) + { + sync = (sync << 8) + buffer.ReadByte(); + if (sync == 0x7FFE8001) + { + syncFound = true; + break; + } + } + if (!syncFound) return; + + int frame_type = buffer.ReadBits(1); + int samples_deficit = buffer.ReadBits(5); + int crc_present = buffer.ReadBits(1); + int sample_blocks = buffer.ReadBits(7); + int frame_size = buffer.ReadBits(14); + if (frame_size < 95) + { + return; + } + int amode = buffer.ReadBits(6); + int sample_rate = buffer.ReadBits(4); + if (sample_rate < 0 || sample_rate >= dca_sample_rates.Length) + { + return; + } + int bit_rate = buffer.ReadBits(5); + if (bit_rate < 0 || bit_rate >= dca_bit_rates.Length) + { + return; + } + int downmix = buffer.ReadBits(1); + int dynrange = buffer.ReadBits(1); + int timestamp = buffer.ReadBits(1); + int aux_data = buffer.ReadBits(1); + int hdcd = buffer.ReadBits(1); + int ext_descr = buffer.ReadBits(3); + int ext_coding = buffer.ReadBits(1); + int aspf = buffer.ReadBits(1); + int lfe = buffer.ReadBits(2); + int predictor_history = buffer.ReadBits(1); + if (crc_present == 1) + { + int crc = buffer.ReadBits(16); + } + int multirate_inter = buffer.ReadBits(1); + int version = buffer.ReadBits(4); + int copy_history = buffer.ReadBits(2); + int source_pcm_res = buffer.ReadBits(3); + int front_sum = buffer.ReadBits(1); + int surround_sum = buffer.ReadBits(1); + int dialog_norm = buffer.ReadBits(4); + if (source_pcm_res < 0 || source_pcm_res >= dca_bits_per_sample.Length) + { + return; + } + int subframes = buffer.ReadBits(4); + int total_channels = buffer.ReadBits(3) + 1 + ext_coding; + + stream.SampleRate = dca_sample_rates[sample_rate]; + stream.ChannelCount = total_channels; + stream.LFE = (lfe > 0 ? 1 : 0); + stream.BitDepth = dca_bits_per_sample[source_pcm_res]; + stream.DialNorm = -dialog_norm; + if ((source_pcm_res & 0x1) == 0x1) + { + stream.AudioMode = TSAudioMode.Extended; + } + + stream.BitRate = (uint)dca_bit_rates[bit_rate]; + switch (stream.BitRate) + { + case 1: + if (bitrate > 0) + { + stream.BitRate = bitrate; + stream.IsVBR = false; + stream.IsInitialized = true; + } + else + { + stream.BitRate = 0; + } + break; + + case 2: + case 3: + stream.IsVBR = true; + stream.IsInitialized = true; + break; + + default: + stream.IsVBR = false; + stream.IsInitialized = true; + break; + } + } + } +} diff --git a/BDInfo/TSCodecDTSHD.cs b/BDInfo/TSCodecDTSHD.cs new file mode 100644 index 0000000000..83cd724ae9 --- /dev/null +++ b/BDInfo/TSCodecDTSHD.cs @@ -0,0 +1,249 @@ +//============================================================================ +// BDInfo - Blu-ray Video and Audio Analysis Tool +// Copyright © 2010 Cinema Squid +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//============================================================================= + +using System; +using System.Collections.Generic; +using System.Text; + +namespace BDInfo +{ + public abstract class TSCodecDTSHD + { + private static int[] SampleRates = new int[] + { 0x1F40, 0x3E80, 0x7D00, 0x0FA00, 0x1F400, 0x5622, 0x0AC44, 0x15888, 0x2B110, 0x56220, 0x2EE0, 0x5DC0, 0x0BB80, 0x17700, 0x2EE00, 0x5DC00 }; + + public static void Scan( + TSAudioStream stream, + TSStreamBuffer buffer, + long bitrate, + ref string tag) + { + if (stream.IsInitialized && + (stream.StreamType == TSStreamType.DTS_HD_SECONDARY_AUDIO || + (stream.CoreStream != null && + stream.CoreStream.IsInitialized))) return; + + bool syncFound = false; + uint sync = 0; + for (int i = 0; i < buffer.Length; i++) + { + sync = (sync << 8) + buffer.ReadByte(); + if (sync == 0x64582025) + { + syncFound = true; + break; + } + } + + if (!syncFound) + { + tag = "CORE"; + if (stream.CoreStream == null) + { + stream.CoreStream = new TSAudioStream(); + stream.CoreStream.StreamType = TSStreamType.DTS_AUDIO; + } + if (!stream.CoreStream.IsInitialized) + { + buffer.BeginRead(); + TSCodecDTS.Scan(stream.CoreStream, buffer, bitrate, ref tag); + } + return; + } + + tag = "HD"; + int temp1 = buffer.ReadBits(8); + int nuSubStreamIndex = buffer.ReadBits(2); + int nuExtSSHeaderSize = 0; + int nuExtSSFSize = 0; + int bBlownUpHeader = buffer.ReadBits(1); + if (1 == bBlownUpHeader) + { + nuExtSSHeaderSize = buffer.ReadBits(12) + 1; + nuExtSSFSize = buffer.ReadBits(20) + 1; + } + else + { + nuExtSSHeaderSize = buffer.ReadBits(8) + 1; + nuExtSSFSize = buffer.ReadBits(16) + 1; + } + int nuNumAudioPresent = 1; + int nuNumAssets = 1; + int bStaticFieldsPresent = buffer.ReadBits(1); + if (1 == bStaticFieldsPresent) + { + int nuRefClockCode = buffer.ReadBits(2); + int nuExSSFrameDurationCode = buffer.ReadBits(3) + 1; + long nuTimeStamp = 0; + if (1 == buffer.ReadBits(1)) + { + nuTimeStamp = (buffer.ReadBits(18) << 18) + buffer.ReadBits(18); + } + nuNumAudioPresent = buffer.ReadBits(3) + 1; + nuNumAssets = buffer.ReadBits(3) + 1; + int[] nuActiveExSSMask = new int[nuNumAudioPresent]; + for (int i = 0; i < nuNumAudioPresent; i++) + { + nuActiveExSSMask[i] = buffer.ReadBits(nuSubStreamIndex + 1); //? + } + for (int i = 0; i < nuNumAudioPresent; i++) + { + for (int j = 0; j < nuSubStreamIndex + 1; j++) + { + if (((j + 1) % 2) == 1) + { + int mask = buffer.ReadBits(8); + } + } + } + if (1 == buffer.ReadBits(1)) + { + int nuMixMetadataAdjLevel = buffer.ReadBits(2); + int nuBits4MixOutMask = buffer.ReadBits(2) * 4 + 4; + int nuNumMixOutConfigs = buffer.ReadBits(2) + 1; + int[] nuMixOutChMask = new int[nuNumMixOutConfigs]; + for (int i = 0; i < nuNumMixOutConfigs; i++) + { + nuMixOutChMask[i] = buffer.ReadBits(nuBits4MixOutMask); + } + } + } + int[] AssetSizes = new int[nuNumAssets]; + for (int i = 0; i < nuNumAssets; i++) + { + if (1 == bBlownUpHeader) + { + AssetSizes[i] = buffer.ReadBits(20) + 1; + } + else + { + AssetSizes[i] = buffer.ReadBits(16) + 1; + } + } + for (int i = 0; i < nuNumAssets; i++) + { + long bufferPosition = buffer.Position; + int nuAssetDescriptorFSIZE = buffer.ReadBits(9) + 1; + int DescriptorDataForAssetIndex = buffer.ReadBits(3); + if (1 == bStaticFieldsPresent) + { + int AssetTypeDescrPresent = buffer.ReadBits(1); + if (1 == AssetTypeDescrPresent) + { + int AssetTypeDescriptor = buffer.ReadBits(4); + } + int LanguageDescrPresent = buffer.ReadBits(1); + if (1 == LanguageDescrPresent) + { + int LanguageDescriptor = buffer.ReadBits(24); + } + int bInfoTextPresent = buffer.ReadBits(1); + if (1 == bInfoTextPresent) + { + int nuInfoTextByteSize = buffer.ReadBits(10) + 1; + int[] InfoText = new int[nuInfoTextByteSize]; + for (int j = 0; j < nuInfoTextByteSize; j++) + { + InfoText[j] = buffer.ReadBits(8); + } + } + int nuBitResolution = buffer.ReadBits(5) + 1; + int nuMaxSampleRate = buffer.ReadBits(4); + int nuTotalNumChs = buffer.ReadBits(8) + 1; + int bOne2OneMapChannels2Speakers = buffer.ReadBits(1); + int nuSpkrActivityMask = 0; + if (1 == bOne2OneMapChannels2Speakers) + { + int bEmbeddedStereoFlag = 0; + if (nuTotalNumChs > 2) + { + bEmbeddedStereoFlag = buffer.ReadBits(1); + } + int bEmbeddedSixChFlag = 0; + if (nuTotalNumChs > 6) + { + bEmbeddedSixChFlag = buffer.ReadBits(1); + } + int bSpkrMaskEnabled = buffer.ReadBits(1); + int nuNumBits4SAMask = 0; + if (1 == bSpkrMaskEnabled) + { + nuNumBits4SAMask = buffer.ReadBits(2); + nuNumBits4SAMask = nuNumBits4SAMask * 4 + 4; + nuSpkrActivityMask = buffer.ReadBits(nuNumBits4SAMask); + } + // TODO... + } + stream.SampleRate = SampleRates[nuMaxSampleRate]; + stream.BitDepth = nuBitResolution; + + stream.LFE = 0; + if ((nuSpkrActivityMask & 0x8) == 0x8) + { + ++stream.LFE; + } + if ((nuSpkrActivityMask & 0x1000) == 0x1000) + { + ++stream.LFE; + } + stream.ChannelCount = nuTotalNumChs - stream.LFE; + } + if (nuNumAssets > 1) + { + // TODO... + break; + } + } + + // TODO + if (stream.CoreStream != null) + { + TSAudioStream coreStream = (TSAudioStream)stream.CoreStream; + if (coreStream.AudioMode == TSAudioMode.Extended && + stream.ChannelCount == 5) + { + stream.AudioMode = TSAudioMode.Extended; + } + /* + if (coreStream.DialNorm != 0) + { + stream.DialNorm = coreStream.DialNorm; + } + */ + } + + if (stream.StreamType == TSStreamType.DTS_HD_MASTER_AUDIO) + { + stream.IsVBR = true; + stream.IsInitialized = true; + } + else if (bitrate > 0) + { + stream.IsVBR = false; + stream.BitRate = bitrate; + if (stream.CoreStream != null) + { + stream.BitRate += stream.CoreStream.BitRate; + stream.IsInitialized = true; + } + stream.IsInitialized = (stream.BitRate > 0 ? true : false); + } + } + } +} diff --git a/BDInfo/TSCodecLPCM.cs b/BDInfo/TSCodecLPCM.cs new file mode 100644 index 0000000000..b9c8385343 --- /dev/null +++ b/BDInfo/TSCodecLPCM.cs @@ -0,0 +1,126 @@ +//============================================================================ +// BDInfo - Blu-ray Video and Audio Analysis Tool +// Copyright © 2010 Cinema Squid +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//============================================================================= + +using System; +using System.Collections.Generic; +using System.Text; + +namespace BDInfo +{ + public abstract class TSCodecLPCM + { + public static void Scan( + TSAudioStream stream, + TSStreamBuffer buffer, + ref string tag) + { + if (stream.IsInitialized) return; + + byte[] header = buffer.ReadBytes(4); + int flags = (header[2] << 8) + header[3]; + + switch ((flags & 0xF000) >> 12) + { + case 1: // 1/0/0 + stream.ChannelCount = 1; + stream.LFE = 0; + break; + case 3: // 2/0/0 + stream.ChannelCount = 2; + stream.LFE = 0; + break; + case 4: // 3/0/0 + stream.ChannelCount = 3; + stream.LFE = 0; + break; + case 5: // 2/1/0 + stream.ChannelCount = 3; + stream.LFE = 0; + break; + case 6: // 3/1/0 + stream.ChannelCount = 4; + stream.LFE = 0; + break; + case 7: // 2/2/0 + stream.ChannelCount = 4; + stream.LFE = 0; + break; + case 8: // 3/2/0 + stream.ChannelCount = 5; + stream.LFE = 0; + break; + case 9: // 3/2/1 + stream.ChannelCount = 5; + stream.LFE = 1; + break; + case 10: // 3/4/0 + stream.ChannelCount = 7; + stream.LFE = 0; + break; + case 11: // 3/4/1 + stream.ChannelCount = 7; + stream.LFE = 1; + break; + default: + stream.ChannelCount = 0; + stream.LFE = 0; + break; + } + + switch ((flags & 0xC0) >> 6) + { + case 1: + stream.BitDepth = 16; + break; + case 2: + stream.BitDepth = 20; + break; + case 3: + stream.BitDepth = 24; + break; + default: + stream.BitDepth = 0; + break; + } + + switch ((flags & 0xF00) >> 8) + { + case 1: + stream.SampleRate = 48000; + break; + case 4: + stream.SampleRate = 96000; + break; + case 5: + stream.SampleRate = 192000; + break; + default: + stream.SampleRate = 0; + break; + } + + stream.BitRate = (uint) + (stream.SampleRate * stream.BitDepth * + (stream.ChannelCount + stream.LFE)); + + stream.IsVBR = false; + stream.IsInitialized = true; + } + } +} diff --git a/BDInfo/TSCodecMPEG2.cs b/BDInfo/TSCodecMPEG2.cs new file mode 100644 index 0000000000..6418a3ad90 --- /dev/null +++ b/BDInfo/TSCodecMPEG2.cs @@ -0,0 +1,211 @@ +//============================================================================ +// BDInfo - Blu-ray Video and Audio Analysis Tool +// Copyright © 2010 Cinema Squid +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//============================================================================= + +#undef DEBUG + +using System; +using System.Collections.Generic; +using System.Text; + +namespace BDInfo +{ + public abstract class TSCodecMPEG2 + { + public static void Scan( + TSVideoStream stream, + TSStreamBuffer buffer, + ref string tag) + { + int parse = 0; + int pictureParse = 0; + int sequenceHeaderParse = 0; + int extensionParse = 0; + int sequenceExtensionParse = 0; + + for (int i = 0; i < buffer.Length; i++) + { + parse = (parse << 8) + buffer.ReadByte(); + + if (parse == 0x00000100) + { + pictureParse = 2; + } + else if (parse == 0x000001B3) + { + sequenceHeaderParse = 7; + } + else if (sequenceHeaderParse > 0) + { + --sequenceHeaderParse; + switch (sequenceHeaderParse) + { +#if DEBUG + case 6: + break; + + case 5: + break; + + case 4: + stream.Width = + (int)((parse & 0xFFF000) >> 12); + stream.Height = + (int)(parse & 0xFFF); + break; + + case 3: + stream.AspectRatio = + (TSAspectRatio)((parse & 0xF0) >> 4); + + switch ((parse & 0xF0) >> 4) + { + case 0: // Forbidden + break; + case 1: // Square + break; + case 2: // 4:3 + break; + case 3: // 16:9 + break; + case 4: // 2.21:1 + break; + default: // Reserved + break; + } + + switch (parse & 0xF) + { + case 0: // Forbidden + break; + case 1: // 23.976 + stream.FrameRateEnumerator = 24000; + stream.FrameRateDenominator = 1001; + break; + case 2: // 24 + stream.FrameRateEnumerator = 24000; + stream.FrameRateDenominator = 1000; + break; + case 3: // 25 + stream.FrameRateEnumerator = 25000; + stream.FrameRateDenominator = 1000; + break; + case 4: // 29.97 + stream.FrameRateEnumerator = 30000; + stream.FrameRateDenominator = 1001; + break; + case 5: // 30 + stream.FrameRateEnumerator = 30000; + stream.FrameRateDenominator = 1000; + break; + case 6: // 50 + stream.FrameRateEnumerator = 50000; + stream.FrameRateDenominator = 1000; + break; + case 7: // 59.94 + stream.FrameRateEnumerator = 60000; + stream.FrameRateDenominator = 1001; + break; + case 8: // 60 + stream.FrameRateEnumerator = 60000; + stream.FrameRateDenominator = 1000; + break; + default: // Reserved + stream.FrameRateEnumerator = 0; + stream.FrameRateDenominator = 0; + break; + } + break; + + case 2: + break; + + case 1: + break; +#endif + + case 0: +#if DEBUG + stream.BitRate = + (((parse & 0xFFFFC0) >> 6) * 200); +#endif + stream.IsVBR = true; + stream.IsInitialized = true; + break; + } + } + else if (pictureParse > 0) + { + --pictureParse; + if (pictureParse == 0) + { + switch ((parse & 0x38) >> 3) + { + case 1: + tag = "I"; + break; + case 2: + tag = "P"; + break; + case 3: + tag = "B"; + break; + default: + break; + } + if (stream.IsInitialized) return; + } + } + else if (parse == 0x000001B5) + { + extensionParse = 1; + } + else if (extensionParse > 0) + { + --extensionParse; + if (extensionParse == 0) + { + if ((parse & 0xF0) == 0x10) + { + sequenceExtensionParse = 1; + } + } + } + else if (sequenceExtensionParse > 0) + { + --sequenceExtensionParse; +#if DEBUG + if (sequenceExtensionParse == 0) + { + uint sequenceExtension = + ((parse & 0x8) >> 3); + if (sequenceExtension == 0) + { + stream.IsInterlaced = true; + } + else + { + stream.IsInterlaced = false; + } + } +#endif + } + } + } + } +} diff --git a/BDInfo/TSCodecMVC.cs b/BDInfo/TSCodecMVC.cs new file mode 100644 index 0000000000..0ad16d0811 --- /dev/null +++ b/BDInfo/TSCodecMVC.cs @@ -0,0 +1,39 @@ +//============================================================================ +// BDInfo - Blu-ray Video and Audio Analysis Tool +// Copyright © 2010 Cinema Squid +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//============================================================================= + +using System; +using System.Collections.Generic; +using System.Text; + +namespace BDInfo +{ + // TODO: Do something more interesting here... + + public abstract class TSCodecMVC + { + public static void Scan( + TSVideoStream stream, + TSStreamBuffer buffer, + ref string tag) + { + stream.IsVBR = true; + stream.IsInitialized = true; + } + } +} diff --git a/BDInfo/TSCodecTrueHD.cs b/BDInfo/TSCodecTrueHD.cs new file mode 100644 index 0000000000..bfe0d99444 --- /dev/null +++ b/BDInfo/TSCodecTrueHD.cs @@ -0,0 +1,189 @@ +//============================================================================ +// BDInfo - Blu-ray Video and Audio Analysis Tool +// Copyright © 2010 Cinema Squid +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//============================================================================= + +using System; +using System.Collections.Generic; +using System.Text; + +namespace BDInfo +{ + public abstract class TSCodecTrueHD + { + public static void Scan( + TSAudioStream stream, + TSStreamBuffer buffer, + ref string tag) + { + if (stream.IsInitialized && + stream.CoreStream != null && + stream.CoreStream.IsInitialized) return; + + bool syncFound = false; + uint sync = 0; + for (int i = 0; i < buffer.Length; i++) + { + sync = (sync << 8) + buffer.ReadByte(); + if (sync == 0xF8726FBA) + { + syncFound = true; + break; + } + } + + if (!syncFound) + { + tag = "CORE"; + if (stream.CoreStream == null) + { + stream.CoreStream = new TSAudioStream(); + stream.CoreStream.StreamType = TSStreamType.AC3_AUDIO; + } + if (!stream.CoreStream.IsInitialized) + { + buffer.BeginRead(); + TSCodecAC3.Scan(stream.CoreStream, buffer, ref tag); + } + return; + } + + tag = "HD"; + int ratebits = buffer.ReadBits(4); + if (ratebits != 0xF) + { + stream.SampleRate = + (((ratebits & 8) > 0 ? 44100 : 48000) << (ratebits & 7)); + } + int temp1 = buffer.ReadBits(8); + int channels_thd_stream1 = buffer.ReadBits(5); + int temp2 = buffer.ReadBits(2); + + stream.ChannelCount = 0; + stream.LFE = 0; + int c_LFE2 = buffer.ReadBits(1); + if (c_LFE2 == 1) + { + stream.LFE += 1; + } + int c_Cvh = buffer.ReadBits(1); + if (c_Cvh == 1) + { + stream.ChannelCount += 1; + } + int c_LRw = buffer.ReadBits(1); + if (c_LRw == 1) + { + stream.ChannelCount += 2; + } + int c_LRsd = buffer.ReadBits(1); + if (c_LRsd == 1) + { + stream.ChannelCount += 2; + } + int c_Ts = buffer.ReadBits(1); + if (c_Ts == 1) + { + stream.ChannelCount += 1; + } + int c_Cs = buffer.ReadBits(1); + if (c_Cs == 1) + { + stream.ChannelCount += 1; + } + int c_LRrs = buffer.ReadBits(1); + if (c_LRrs == 1) + { + stream.ChannelCount += 2; + } + int c_LRc = buffer.ReadBits(1); + if (c_LRc == 1) + { + stream.ChannelCount += 2; + } + int c_LRvh = buffer.ReadBits(1); + if (c_LRvh == 1) + { + stream.ChannelCount += 2; + } + int c_LRs = buffer.ReadBits(1); + if (c_LRs == 1) + { + stream.ChannelCount += 2; + } + int c_LFE = buffer.ReadBits(1); + if (c_LFE == 1) + { + stream.LFE += 1; + } + int c_C = buffer.ReadBits(1); + if (c_C == 1) + { + stream.ChannelCount += 1; + } + int c_LR = buffer.ReadBits(1); + if (c_LR == 1) + { + stream.ChannelCount += 2; + } + + int access_unit_size = 40 << (ratebits & 7); + int access_unit_size_pow2 = 64 << (ratebits & 7); + + int a1 = buffer.ReadBits(16); + int a2 = buffer.ReadBits(16); + int a3 = buffer.ReadBits(16); + + int is_vbr = buffer.ReadBits(1); + int peak_bitrate = buffer.ReadBits(15); + peak_bitrate = (peak_bitrate * stream.SampleRate) >> 4; + + double peak_bitdepth = + (double)peak_bitrate / + (stream.ChannelCount + stream.LFE) / + stream.SampleRate; + if (peak_bitdepth > 14) + { + stream.BitDepth = 24; + } + else + { + stream.BitDepth = 16; + } + +#if DEBUG + System.Diagnostics.Debug.WriteLine(string.Format( + "{0}\t{1}\t{2:F2}", + stream.PID, peak_bitrate, peak_bitdepth)); +#endif + /* + // TODO: Get THD dialnorm from metadata + if (stream.CoreStream != null) + { + TSAudioStream coreStream = (TSAudioStream)stream.CoreStream; + if (coreStream.DialNorm != 0) + { + stream.DialNorm = coreStream.DialNorm; + } + } + */ + + stream.IsVBR = true; + stream.IsInitialized = true; + } + } +} diff --git a/BDInfo/TSCodecVC1.cs b/BDInfo/TSCodecVC1.cs new file mode 100644 index 0000000000..3cfd50cba0 --- /dev/null +++ b/BDInfo/TSCodecVC1.cs @@ -0,0 +1,134 @@ +//============================================================================ +// BDInfo - Blu-ray Video and Audio Analysis Tool +// Copyright © 2010 Cinema Squid +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//============================================================================= + +using System; +using System.Collections.Generic; +using System.Text; + +namespace BDInfo +{ + public abstract class TSCodecVC1 + { + public static void Scan( + TSVideoStream stream, + TSStreamBuffer buffer, + ref string tag) + { + int parse = 0; + byte frameHeaderParse = 0; + byte sequenceHeaderParse = 0; + bool isInterlaced = false; + + for (int i = 0; i < buffer.Length; i++) + { + parse = (parse << 8) + buffer.ReadByte(); + + if (parse == 0x0000010D) + { + frameHeaderParse = 4; + } + else if (frameHeaderParse > 0) + { + --frameHeaderParse; + if (frameHeaderParse == 0) + { + uint pictureType = 0; + if (isInterlaced) + { + if ((parse & 0x80000000) == 0) + { + pictureType = + (uint)((parse & 0x78000000) >> 13); + } + else + { + pictureType = + (uint)((parse & 0x3c000000) >> 12); + } + } + else + { + pictureType = + (uint)((parse & 0xf0000000) >> 14); + } + + if ((pictureType & 0x20000) == 0) + { + tag = "P"; + } + else if ((pictureType & 0x10000) == 0) + { + tag = "B"; + } + else if ((pictureType & 0x8000) == 0) + { + tag = "I"; + } + else if ((pictureType & 0x4000) == 0) + { + tag = "BI"; + } + else + { + tag = null; + } + if (stream.IsInitialized) return; + } + } + else if (parse == 0x0000010F) + { + sequenceHeaderParse = 6; + } + else if (sequenceHeaderParse > 0) + { + --sequenceHeaderParse; + switch (sequenceHeaderParse) + { + case 5: + int profileLevel = ((parse & 0x38) >> 3); + if (((parse & 0xC0) >> 6) == 3) + { + stream.EncodingProfile = string.Format( + "Advanced Profile {0}", profileLevel); + } + else + { + stream.EncodingProfile = string.Format( + "Main Profile {0}", profileLevel); + } + break; + + case 0: + if (((parse & 0x40) >> 6) > 0) + { + isInterlaced = true; + } + else + { + isInterlaced = false; + } + break; + } + stream.IsVBR = true; + stream.IsInitialized = true; + } + } + } + } +} diff --git a/BDInfo/TSInterleavedFile.cs b/BDInfo/TSInterleavedFile.cs new file mode 100644 index 0000000000..27049e6602 --- /dev/null +++ b/BDInfo/TSInterleavedFile.cs @@ -0,0 +1,40 @@ +//============================================================================ +// BDInfo - Blu-ray Video and Audio Analysis Tool +// Copyright © 2010 Cinema Squid +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//============================================================================= + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +// TODO: Do more interesting things here... + +namespace BDInfo +{ + public class TSInterleavedFile + { + public FileInfo FileInfo = null; + public string Name = null; + + public TSInterleavedFile(FileInfo fileInfo) + { + FileInfo = fileInfo; + Name = fileInfo.Name.ToUpper(); + } + } +} diff --git a/BDInfo/TSPlaylistFile.cs b/BDInfo/TSPlaylistFile.cs new file mode 100644 index 0000000000..1d00a78450 --- /dev/null +++ b/BDInfo/TSPlaylistFile.cs @@ -0,0 +1,1287 @@ +//============================================================================ +// BDInfo - Blu-ray Video and Audio Analysis Tool +// Copyright © 2010 Cinema Squid +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//============================================================================= + +#undef DEBUG +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace BDInfo +{ + public class TSPlaylistFile + { + private FileInfo FileInfo = null; + public string FileType = null; + public bool IsInitialized = false; + public string Name = null; + public BDROM BDROM = null; + public bool HasHiddenTracks = false; + public bool HasLoops = false; + public bool IsCustom = false; + + public List Chapters = new List(); + + public Dictionary Streams = + new Dictionary(); + public Dictionary PlaylistStreams = + new Dictionary(); + public List StreamClips = + new List(); + public List> AngleStreams = + new List>(); + public List> AngleClips = + new List>(); + public int AngleCount = 0; + + public List SortedStreams = + new List(); + public List VideoStreams = + new List(); + public List AudioStreams = + new List(); + public List TextStreams = + new List(); + public List GraphicsStreams = + new List(); + + public TSPlaylistFile( + BDROM bdrom, + FileInfo fileInfo) + { + BDROM = bdrom; + FileInfo = fileInfo; + Name = fileInfo.Name.ToUpper(); + } + + public TSPlaylistFile( + BDROM bdrom, + string name, + List clips) + { + BDROM = bdrom; + Name = name; + IsCustom = true; + foreach (TSStreamClip clip in clips) + { + TSStreamClip newClip = new TSStreamClip( + clip.StreamFile, clip.StreamClipFile); + + newClip.Name = clip.Name; + newClip.TimeIn = clip.TimeIn; + newClip.TimeOut = clip.TimeOut; + newClip.Length = newClip.TimeOut - newClip.TimeIn; + newClip.RelativeTimeIn = TotalLength; + newClip.RelativeTimeOut = newClip.RelativeTimeIn + newClip.Length; + newClip.AngleIndex = clip.AngleIndex; + newClip.Chapters.Add(clip.TimeIn); + StreamClips.Add(newClip); + + if (newClip.AngleIndex > AngleCount) + { + AngleCount = newClip.AngleIndex; + } + if (newClip.AngleIndex == 0) + { + Chapters.Add(newClip.RelativeTimeIn); + } + } + LoadStreamClips(); + IsInitialized = true; + } + + public override string ToString() + { + return Name; + } + + public ulong InterleavedFileSize + { + get + { + ulong size = 0; + foreach (TSStreamClip clip in StreamClips) + { + size += clip.InterleavedFileSize; + } + return size; + } + } + public ulong FileSize + { + get + { + ulong size = 0; + foreach (TSStreamClip clip in StreamClips) + { + size += clip.FileSize; + } + return size; + } + } + public double TotalLength + { + get + { + double length = 0; + foreach (TSStreamClip clip in StreamClips) + { + if (clip.AngleIndex == 0) + { + length += clip.Length; + } + } + return length; + } + } + + public double TotalAngleLength + { + get + { + double length = 0; + foreach (TSStreamClip clip in StreamClips) + { + length += clip.Length; + } + return length; + } + } + + public ulong TotalSize + { + get + { + ulong size = 0; + foreach (TSStreamClip clip in StreamClips) + { + if (clip.AngleIndex == 0) + { + size += clip.PacketSize; + } + } + return size; + } + } + + public ulong TotalAngleSize + { + get + { + ulong size = 0; + foreach (TSStreamClip clip in StreamClips) + { + size += clip.PacketSize; + } + return size; + } + } + + public ulong TotalBitRate + { + get + { + if (TotalLength > 0) + { + return (ulong)Math.Round(((TotalSize * 8.0) / TotalLength)); + } + return 0; + } + } + + public ulong TotalAngleBitRate + { + get + { + if (TotalAngleLength > 0) + { + return (ulong)Math.Round(((TotalAngleSize * 8.0) / TotalAngleLength)); + } + return 0; + } + } + + public void Scan( + Dictionary streamFiles, + Dictionary streamClipFiles) + { + FileStream fileStream = null; + BinaryReader fileReader = null; + + try + { + Streams.Clear(); + StreamClips.Clear(); + + fileStream = File.OpenRead(FileInfo.FullName); + fileReader = new BinaryReader(fileStream); + + byte[] data = new byte[fileStream.Length]; + int dataLength = fileReader.Read(data, 0, data.Length); + + int pos = 0; + + FileType = ReadString(data, 8, ref pos); + if (FileType != "MPLS0100" && FileType != "MPLS0200") + { + throw new Exception(string.Format( + "Playlist {0} has an unknown file type {1}.", + FileInfo.Name, FileType)); + } + + int playlistOffset = ReadInt32(data, ref pos); + int chaptersOffset = ReadInt32(data, ref pos); + int extensionsOffset = ReadInt32(data, ref pos); + + pos = playlistOffset; + + int playlistLength = ReadInt32(data, ref pos); + int playlistReserved = ReadInt16(data, ref pos); + int itemCount = ReadInt16(data, ref pos); + int subitemCount = ReadInt16(data, ref pos); + + List chapterClips = new List(); + for (int itemIndex = 0; itemIndex < itemCount; itemIndex++) + { + int itemStart = pos; + int itemLength = ReadInt16(data, ref pos); + string itemName = ReadString(data, 5, ref pos); + string itemType = ReadString(data, 4, ref pos); + + TSStreamFile streamFile = null; + string streamFileName = string.Format( + "{0}.M2TS", itemName); + if (streamFiles.ContainsKey(streamFileName)) + { + streamFile = streamFiles[streamFileName]; + } + if (streamFile == null) + { + Debug.WriteLine(string.Format( + "Playlist {0} referenced missing file {1}.", + FileInfo.Name, streamFileName)); + } + + TSStreamClipFile streamClipFile = null; + string streamClipFileName = string.Format( + "{0}.CLPI", itemName); + if (streamClipFiles.ContainsKey(streamClipFileName)) + { + streamClipFile = streamClipFiles[streamClipFileName]; + } + if (streamClipFile == null) + { + throw new Exception(string.Format( + "Playlist {0} referenced missing file {1}.", + FileInfo.Name, streamFileName)); + } + + pos += 1; + int multiangle = (data[pos] >> 4) & 0x01; + int condition = data[pos] & 0x0F; + pos += 2; + + int inTime = ReadInt32(data, ref pos); + if (inTime < 0) inTime &= 0x7FFFFFFF; + double timeIn = (double)inTime / 45000; + + int outTime = ReadInt32(data, ref pos); + if (outTime < 0) outTime &= 0x7FFFFFFF; + double timeOut = (double)outTime / 45000; + + TSStreamClip streamClip = new TSStreamClip( + streamFile, streamClipFile); + + streamClip.Name = streamFileName; //TODO + streamClip.TimeIn = timeIn; + streamClip.TimeOut = timeOut; + streamClip.Length = streamClip.TimeOut - streamClip.TimeIn; + streamClip.RelativeTimeIn = TotalLength; + streamClip.RelativeTimeOut = streamClip.RelativeTimeIn + streamClip.Length; + StreamClips.Add(streamClip); + chapterClips.Add(streamClip); + + pos += 12; + if (multiangle > 0) + { + int angles = data[pos]; + pos += 2; + for (int angle = 0; angle < angles - 1; angle++) + { + string angleName = ReadString(data, 5, ref pos); + string angleType = ReadString(data, 4, ref pos); + pos += 1; + + TSStreamFile angleFile = null; + string angleFileName = string.Format( + "{0}.M2TS", angleName); + if (streamFiles.ContainsKey(angleFileName)) + { + angleFile = streamFiles[angleFileName]; + } + if (angleFile == null) + { + throw new Exception(string.Format( + "Playlist {0} referenced missing angle file {1}.", + FileInfo.Name, angleFileName)); + } + + TSStreamClipFile angleClipFile = null; + string angleClipFileName = string.Format( + "{0}.CLPI", angleName); + if (streamClipFiles.ContainsKey(angleClipFileName)) + { + angleClipFile = streamClipFiles[angleClipFileName]; + } + if (angleClipFile == null) + { + throw new Exception(string.Format( + "Playlist {0} referenced missing angle file {1}.", + FileInfo.Name, angleClipFileName)); + } + + TSStreamClip angleClip = + new TSStreamClip(angleFile, angleClipFile); + angleClip.AngleIndex = angle + 1; + angleClip.TimeIn = streamClip.TimeIn; + angleClip.TimeOut = streamClip.TimeOut; + angleClip.RelativeTimeIn = streamClip.RelativeTimeIn; + angleClip.RelativeTimeOut = streamClip.RelativeTimeOut; + angleClip.Length = streamClip.Length; + StreamClips.Add(angleClip); + } + if (angles - 1 > AngleCount) AngleCount = angles - 1; + } + + int streamInfoLength = ReadInt16(data, ref pos); + pos += 2; + int streamCountVideo = data[pos++]; + int streamCountAudio = data[pos++]; + int streamCountPG = data[pos++]; + int streamCountIG = data[pos++]; + int streamCountSecondaryAudio = data[pos++]; + int streamCountSecondaryVideo = data[pos++]; + int streamCountPIP = data[pos++]; + pos += 5; + +#if DEBUG + Debug.WriteLine(string.Format( + "{0} : {1} -> V:{2} A:{3} PG:{4} IG:{5} 2A:{6} 2V:{7} PIP:{8}", + Name, streamFileName, streamCountVideo, streamCountAudio, streamCountPG, streamCountIG, + streamCountSecondaryAudio, streamCountSecondaryVideo, streamCountPIP)); +#endif + + for (int i = 0; i < streamCountVideo; i++) + { + TSStream stream = CreatePlaylistStream(data, ref pos); + if (stream != null) PlaylistStreams[stream.PID] = stream; + } + for (int i = 0; i < streamCountAudio; i++) + { + TSStream stream = CreatePlaylistStream(data, ref pos); + if (stream != null) PlaylistStreams[stream.PID] = stream; + } + for (int i = 0; i < streamCountPG; i++) + { + TSStream stream = CreatePlaylistStream(data, ref pos); + if (stream != null) PlaylistStreams[stream.PID] = stream; + } + for (int i = 0; i < streamCountIG; i++) + { + TSStream stream = CreatePlaylistStream(data, ref pos); + if (stream != null) PlaylistStreams[stream.PID] = stream; + } + for (int i = 0; i < streamCountSecondaryAudio; i++) + { + TSStream stream = CreatePlaylistStream(data, ref pos); + if (stream != null) PlaylistStreams[stream.PID] = stream; + pos += 2; + } + for (int i = 0; i < streamCountSecondaryVideo; i++) + { + TSStream stream = CreatePlaylistStream(data, ref pos); + if (stream != null) PlaylistStreams[stream.PID] = stream; + pos += 6; + } + /* + * TODO + * + for (int i = 0; i < streamCountPIP; i++) + { + TSStream stream = CreatePlaylistStream(data, ref pos); + if (stream != null) PlaylistStreams[stream.PID] = stream; + } + */ + + pos += itemLength - (pos - itemStart) + 2; + } + + pos = chaptersOffset + 4; + + int chapterCount = ReadInt16(data, ref pos); + + for (int chapterIndex = 0; + chapterIndex < chapterCount; + chapterIndex++) + { + int chapterType = data[pos+1]; + + if (chapterType == 1) + { + int streamFileIndex = + ((int)data[pos + 2] << 8) + data[pos + 3]; + + long chapterTime = + ((long)data[pos + 4] << 24) + + ((long)data[pos + 5] << 16) + + ((long)data[pos + 6] << 8) + + ((long)data[pos + 7]); + + TSStreamClip streamClip = chapterClips[streamFileIndex]; + + double chapterSeconds = (double)chapterTime / 45000; + + double relativeSeconds = + chapterSeconds - + streamClip.TimeIn + + streamClip.RelativeTimeIn; + + // TODO: Ignore short last chapter? + if (TotalLength - relativeSeconds > 1.0) + { + streamClip.Chapters.Add(chapterSeconds); + this.Chapters.Add(relativeSeconds); + } + } + else + { + // TODO: Handle other chapter types? + } + pos += 14; + } + } + finally + { + if (fileReader != null) + { + fileReader.Close(); + } + if (fileStream != null) + { + fileStream.Close(); + } + } + } + + public void Initialize() + { + LoadStreamClips(); + + Dictionary> clipTimes = new Dictionary>(); + foreach (TSStreamClip clip in StreamClips) + { + if (clip.AngleIndex == 0) + { + if (clipTimes.ContainsKey(clip.Name)) + { + if (clipTimes[clip.Name].Contains(clip.TimeIn)) + { + HasLoops = true; + break; + } + else + { + clipTimes[clip.Name].Add(clip.TimeIn); + } + } + else + { + clipTimes[clip.Name] = new List { clip.TimeIn }; + } + } + } + ClearBitrates(); + IsInitialized = true; + } + + protected TSStream CreatePlaylistStream(byte[] data, ref int pos) + { + TSStream stream = null; + + int start = pos; + + int headerLength = data[pos++]; + int headerPos = pos; + int headerType = data[pos++]; + + int pid = 0; + int subpathid = 0; + int subclipid = 0; + + switch (headerType) + { + case 1: + pid = ReadInt16(data, ref pos); + break; + case 2: + subpathid = data[pos++]; + subclipid = data[pos++]; + pid = ReadInt16(data, ref pos); + break; + case 3: + subpathid = data[pos++]; + pid = ReadInt16(data, ref pos); + break; + case 4: + subpathid = data[pos++]; + subclipid = data[pos++]; + pid = ReadInt16(data, ref pos); + break; + default: + break; + } + + pos = headerPos + headerLength; + + int streamLength = data[pos++]; + int streamPos = pos; + + TSStreamType streamType = (TSStreamType)data[pos++]; + switch (streamType) + { + case TSStreamType.MVC_VIDEO: + // TODO + break; + + case TSStreamType.AVC_VIDEO: + case TSStreamType.MPEG1_VIDEO: + case TSStreamType.MPEG2_VIDEO: + case TSStreamType.VC1_VIDEO: + + TSVideoFormat videoFormat = (TSVideoFormat) + (data[pos] >> 4); + TSFrameRate frameRate = (TSFrameRate) + (data[pos] & 0xF); + TSAspectRatio aspectRatio = (TSAspectRatio) + (data[pos + 1] >> 4); + + stream = new TSVideoStream(); + ((TSVideoStream)stream).VideoFormat = videoFormat; + ((TSVideoStream)stream).AspectRatio = aspectRatio; + ((TSVideoStream)stream).FrameRate = frameRate; + +#if DEBUG + Debug.WriteLine(string.Format( + "\t{0} {1} {2} {3} {4}", + pid, + streamType, + videoFormat, + frameRate, + aspectRatio)); +#endif + + break; + + case TSStreamType.AC3_AUDIO: + case TSStreamType.AC3_PLUS_AUDIO: + case TSStreamType.AC3_PLUS_SECONDARY_AUDIO: + case TSStreamType.AC3_TRUE_HD_AUDIO: + case TSStreamType.DTS_AUDIO: + case TSStreamType.DTS_HD_AUDIO: + case TSStreamType.DTS_HD_MASTER_AUDIO: + case TSStreamType.DTS_HD_SECONDARY_AUDIO: + case TSStreamType.LPCM_AUDIO: + case TSStreamType.MPEG1_AUDIO: + case TSStreamType.MPEG2_AUDIO: + + int audioFormat = ReadByte(data, ref pos); + + TSChannelLayout channelLayout = (TSChannelLayout) + (audioFormat >> 4); + TSSampleRate sampleRate = (TSSampleRate) + (audioFormat & 0xF); + + string audioLanguage = ReadString(data, 3, ref pos); + + stream = new TSAudioStream(); + ((TSAudioStream)stream).ChannelLayout = channelLayout; + ((TSAudioStream)stream).SampleRate = TSAudioStream.ConvertSampleRate(sampleRate); + ((TSAudioStream)stream).LanguageCode = audioLanguage; + +#if DEBUG + Debug.WriteLine(string.Format( + "\t{0} {1} {2} {3} {4}", + pid, + streamType, + audioLanguage, + channelLayout, + sampleRate)); +#endif + + break; + + case TSStreamType.INTERACTIVE_GRAPHICS: + case TSStreamType.PRESENTATION_GRAPHICS: + + string graphicsLanguage = ReadString(data, 3, ref pos); + + stream = new TSGraphicsStream(); + ((TSGraphicsStream)stream).LanguageCode = graphicsLanguage; + + if (data[pos] != 0) + { + } + +#if DEBUG + Debug.WriteLine(string.Format( + "\t{0} {1} {2}", + pid, + streamType, + graphicsLanguage)); +#endif + + break; + + case TSStreamType.SUBTITLE: + + int code = ReadByte(data, ref pos); // TODO + string textLanguage = ReadString(data, 3, ref pos); + + stream = new TSTextStream(); + ((TSTextStream)stream).LanguageCode = textLanguage; + +#if DEBUG + Debug.WriteLine(string.Format( + "\t{0} {1} {2}", + pid, + streamType, + textLanguage)); +#endif + + break; + + default: + break; + } + + pos = streamPos + streamLength; + + if (stream != null) + { + stream.PID = (ushort)pid; + stream.StreamType = streamType; + } + + return stream; + } + + private void LoadStreamClips() + { + AngleClips.Clear(); + if (AngleCount > 0) + { + for (int angleIndex = 0; angleIndex < AngleCount; angleIndex++) + { + AngleClips.Add(new Dictionary()); + } + } + + TSStreamClip referenceClip = null; + if (StreamClips.Count > 0) + { + referenceClip = StreamClips[0]; + } + foreach (TSStreamClip clip in StreamClips) + { + if (clip.StreamClipFile.Streams.Count > referenceClip.StreamClipFile.Streams.Count) + { + referenceClip = clip; + } + else if (clip.Length > referenceClip.Length) + { + referenceClip = clip; + } + if (AngleCount > 0) + { + if (clip.AngleIndex == 0) + { + for (int angleIndex = 0; angleIndex < AngleCount; angleIndex++) + { + AngleClips[angleIndex][clip.RelativeTimeIn] = clip; + } + } + else + { + AngleClips[clip.AngleIndex - 1][clip.RelativeTimeIn] = clip; + } + } + } + + foreach (TSStream clipStream + in referenceClip.StreamClipFile.Streams.Values) + { + if (!Streams.ContainsKey(clipStream.PID)) + { + TSStream stream = clipStream.Clone(); + Streams[clipStream.PID] = stream; + + if (!IsCustom && !PlaylistStreams.ContainsKey(stream.PID)) + { + stream.IsHidden = true; + HasHiddenTracks = true; + } + + if (stream.IsVideoStream) + { + VideoStreams.Add((TSVideoStream)stream); + } + else if (stream.IsAudioStream) + { + AudioStreams.Add((TSAudioStream)stream); + } + else if (stream.IsGraphicsStream) + { + GraphicsStreams.Add((TSGraphicsStream)stream); + } + else if (stream.IsTextStream) + { + TextStreams.Add((TSTextStream)stream); + } + } + } + + if (referenceClip.StreamFile != null) + { + // TODO: Better way to add this in? + if (BDInfoSettings.EnableSSIF && + referenceClip.StreamFile.InterleavedFile != null && + referenceClip.StreamFile.Streams.ContainsKey(4114) && + !Streams.ContainsKey(4114)) + { + TSStream stream = referenceClip.StreamFile.Streams[4114].Clone(); + Streams[4114] = stream; + if (stream.IsVideoStream) + { + VideoStreams.Add((TSVideoStream)stream); + } + } + + foreach (TSStream clipStream + in referenceClip.StreamFile.Streams.Values) + { + if (Streams.ContainsKey(clipStream.PID)) + { + TSStream stream = Streams[clipStream.PID]; + + if (stream.StreamType != clipStream.StreamType) continue; + + if (clipStream.BitRate > stream.BitRate) + { + stream.BitRate = clipStream.BitRate; + } + stream.IsVBR = clipStream.IsVBR; + + if (stream.IsVideoStream && + clipStream.IsVideoStream) + { + ((TSVideoStream)stream).EncodingProfile = + ((TSVideoStream)clipStream).EncodingProfile; + } + else if (stream.IsAudioStream && + clipStream.IsAudioStream) + { + TSAudioStream audioStream = (TSAudioStream)stream; + TSAudioStream clipAudioStream = (TSAudioStream)clipStream; + + if (clipAudioStream.ChannelCount > audioStream.ChannelCount) + { + audioStream.ChannelCount = clipAudioStream.ChannelCount; + } + if (clipAudioStream.LFE > audioStream.LFE) + { + audioStream.LFE = clipAudioStream.LFE; + } + if (clipAudioStream.SampleRate > audioStream.SampleRate) + { + audioStream.SampleRate = clipAudioStream.SampleRate; + } + if (clipAudioStream.BitDepth > audioStream.BitDepth) + { + audioStream.BitDepth = clipAudioStream.BitDepth; + } + if (clipAudioStream.DialNorm < audioStream.DialNorm) + { + audioStream.DialNorm = clipAudioStream.DialNorm; + } + if (clipAudioStream.AudioMode != TSAudioMode.Unknown) + { + audioStream.AudioMode = clipAudioStream.AudioMode; + } + if (clipAudioStream.CoreStream != null && + audioStream.CoreStream == null) + { + audioStream.CoreStream = (TSAudioStream) + clipAudioStream.CoreStream.Clone(); + } + } + } + } + } + + for (int i = 0; i < AngleCount; i++) + { + AngleStreams.Add(new Dictionary()); + } + + if (!BDInfoSettings.KeepStreamOrder) + { + VideoStreams.Sort(CompareVideoStreams); + } + foreach (TSStream stream in VideoStreams) + { + SortedStreams.Add(stream); + for (int i = 0; i < AngleCount; i++) + { + TSStream angleStream = stream.Clone(); + angleStream.AngleIndex = i + 1; + AngleStreams[i][angleStream.PID] = angleStream; + SortedStreams.Add(angleStream); + } + } + + if (!BDInfoSettings.KeepStreamOrder) + { + AudioStreams.Sort(CompareAudioStreams); + } + foreach (TSStream stream in AudioStreams) + { + SortedStreams.Add(stream); + } + + if (!BDInfoSettings.KeepStreamOrder) + { + GraphicsStreams.Sort(CompareGraphicsStreams); + } + foreach (TSStream stream in GraphicsStreams) + { + SortedStreams.Add(stream); + } + + if (!BDInfoSettings.KeepStreamOrder) + { + TextStreams.Sort(CompareTextStreams); + } + foreach (TSStream stream in TextStreams) + { + SortedStreams.Add(stream); + } + } + + public void ClearBitrates() + { + foreach (TSStreamClip clip in StreamClips) + { + clip.PayloadBytes = 0; + clip.PacketCount = 0; + clip.PacketSeconds = 0; + + if (clip.StreamFile != null) + { + foreach (TSStream stream in clip.StreamFile.Streams.Values) + { + stream.PayloadBytes = 0; + stream.PacketCount = 0; + stream.PacketSeconds = 0; + } + + if (clip.StreamFile != null && + clip.StreamFile.StreamDiagnostics != null) + { + clip.StreamFile.StreamDiagnostics.Clear(); + } + } + } + + foreach (TSStream stream in SortedStreams) + { + stream.PayloadBytes = 0; + stream.PacketCount = 0; + stream.PacketSeconds = 0; + } + } + + public bool IsValid + { + get + { + if (!IsInitialized) return false; + + if (BDInfoSettings.FilterShortPlaylists && + TotalLength < BDInfoSettings.FilterShortPlaylistsValue) + { + return false; + } + + if (HasLoops && + BDInfoSettings.FilterLoopingPlaylists) + { + return false; + } + + return true; + } + } + + public static int CompareVideoStreams( + TSVideoStream x, + TSVideoStream y) + { + if (x == null && y == null) + { + return 0; + } + else if (x == null && y != null) + { + return 1; + } + else if (x != null && y == null) + { + return -1; + } + else + { + if (x.Height > y.Height) + { + return -1; + } + else if (y.Height > x.Height) + { + return 1; + } + else if (x.PID > y.PID) + { + return 1; + } + else if (y.PID > x.PID) + { + return -1; + } + else + { + return 0; + } + } + } + + public static int CompareAudioStreams( + TSAudioStream x, + TSAudioStream y) + { + if (x == y) + { + return 0; + } + else if (x == null && y == null) + { + return 0; + } + else if (x == null && y != null) + { + return -1; + } + else if (x != null && y == null) + { + return 1; + } + else + { + if (x.ChannelCount > y.ChannelCount) + { + return -1; + } + else if (y.ChannelCount > x.ChannelCount) + { + return 1; + } + else + { + int sortX = GetStreamTypeSortIndex(x.StreamType); + int sortY = GetStreamTypeSortIndex(y.StreamType); + + if (sortX > sortY) + { + return -1; + } + else if (sortY > sortX) + { + return 1; + } + else + { + if (x.LanguageCode == "eng") + { + return -1; + } + else if (y.LanguageCode == "eng") + { + return 1; + } + else if (x.LanguageCode != y.LanguageCode) + { + return string.Compare( + x.LanguageName, y.LanguageName); + } + else if (x.PID < y.PID) + { + return -1; + } + else if (y.PID < x.PID) + { + return 1; + } + return 0; + } + } + } + } + + public static int CompareTextStreams( + TSTextStream x, + TSTextStream y) + { + if (x == y) + { + return 0; + } + else if (x == null && y == null) + { + return 0; + } + else if (x == null && y != null) + { + return -1; + } + else if (x != null && y == null) + { + return 1; + } + else + { + if (x.LanguageCode == "eng") + { + return -1; + } + else if (y.LanguageCode == "eng") + { + return 1; + } + else + { + if (x.LanguageCode == y.LanguageCode) + { + if (x.PID > y.PID) + { + return 1; + } + else if (y.PID > x.PID) + { + return -1; + } + else + { + return 0; + } + } + else + { + return string.Compare( + x.LanguageName, y.LanguageName); + } + } + } + } + + private static int CompareGraphicsStreams( + TSGraphicsStream x, + TSGraphicsStream y) + { + if (x == y) + { + return 0; + } + else if (x == null && y == null) + { + return 0; + } + else if (x == null && y != null) + { + return -1; + } + else if (x != null && y == null) + { + return 1; + } + else + { + int sortX = GetStreamTypeSortIndex(x.StreamType); + int sortY = GetStreamTypeSortIndex(y.StreamType); + + if (sortX > sortY) + { + return -1; + } + else if (sortY > sortX) + { + return 1; + } + else if (x.LanguageCode == "eng") + { + return -1; + } + else if (y.LanguageCode == "eng") + { + return 1; + } + else + { + if (x.LanguageCode == y.LanguageCode) + { + if (x.PID > y.PID) + { + return 1; + } + else if (y.PID > x.PID) + { + return -1; + } + else + { + return 0; + } + } + else + { + return string.Compare(x.LanguageName, y.LanguageName); + } + } + } + } + + private static int GetStreamTypeSortIndex(TSStreamType streamType) + { + switch (streamType) + { + case TSStreamType.Unknown: + return 0; + case TSStreamType.MPEG1_VIDEO: + return 1; + case TSStreamType.MPEG2_VIDEO: + return 2; + case TSStreamType.AVC_VIDEO: + return 3; + case TSStreamType.VC1_VIDEO: + return 4; + case TSStreamType.MVC_VIDEO: + return 5; + + case TSStreamType.MPEG1_AUDIO: + return 1; + case TSStreamType.MPEG2_AUDIO: + return 2; + case TSStreamType.AC3_PLUS_SECONDARY_AUDIO: + return 3; + case TSStreamType.DTS_HD_SECONDARY_AUDIO: + return 4; + case TSStreamType.AC3_AUDIO: + return 5; + case TSStreamType.DTS_AUDIO: + return 6; + case TSStreamType.AC3_PLUS_AUDIO: + return 7; + case TSStreamType.DTS_HD_AUDIO: + return 8; + case TSStreamType.AC3_TRUE_HD_AUDIO: + return 9; + case TSStreamType.DTS_HD_MASTER_AUDIO: + return 10; + case TSStreamType.LPCM_AUDIO: + return 11; + + case TSStreamType.SUBTITLE: + return 1; + case TSStreamType.INTERACTIVE_GRAPHICS: + return 2; + case TSStreamType.PRESENTATION_GRAPHICS: + return 3; + + default: + return 0; + } + } + + protected string ReadString( + byte[] data, + int count, + ref int pos) + { + string val = + ASCIIEncoding.ASCII.GetString(data, pos, count); + + pos += count; + + return val; + } + + protected int ReadInt32( + byte[] data, + ref int pos) + { + int val = + ((int)data[pos] << 24) + + ((int)data[pos + 1] << 16) + + ((int)data[pos + 2] << 8) + + ((int)data[pos + 3]); + + pos += 4; + + return val; + } + + protected int ReadInt16( + byte[] data, + ref int pos) + { + int val = + ((int)data[pos] << 8) + + ((int)data[pos + 1]); + + pos += 2; + + return val; + } + + protected byte ReadByte( + byte[] data, + ref int pos) + { + return data[pos++]; + } + } +} diff --git a/BDInfo/TSStream.cs b/BDInfo/TSStream.cs new file mode 100644 index 0000000000..5324acae4c --- /dev/null +++ b/BDInfo/TSStream.cs @@ -0,0 +1,802 @@ +//============================================================================ +// BDInfo - Blu-ray Video and Audio Analysis Tool +// Copyright © 2010 Cinema Squid +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//============================================================================= + +using System; +using System.Collections.Generic; +using System.Text; + +namespace BDInfo +{ + public enum TSStreamType : byte + { + Unknown = 0, + MPEG1_VIDEO = 0x01, + MPEG2_VIDEO = 0x02, + AVC_VIDEO = 0x1b, + MVC_VIDEO = 0x20, + VC1_VIDEO = 0xea, + MPEG1_AUDIO = 0x03, + MPEG2_AUDIO = 0x04, + LPCM_AUDIO = 0x80, + AC3_AUDIO = 0x81, + AC3_PLUS_AUDIO = 0x84, + AC3_PLUS_SECONDARY_AUDIO = 0xA1, + AC3_TRUE_HD_AUDIO = 0x83, + DTS_AUDIO = 0x82, + DTS_HD_AUDIO = 0x85, + DTS_HD_SECONDARY_AUDIO = 0xA2, + DTS_HD_MASTER_AUDIO = 0x86, + PRESENTATION_GRAPHICS = 0x90, + INTERACTIVE_GRAPHICS = 0x91, + SUBTITLE = 0x92 + } + + public enum TSVideoFormat : byte + { + Unknown = 0, + VIDEOFORMAT_480i = 1, + VIDEOFORMAT_576i = 2, + VIDEOFORMAT_480p = 3, + VIDEOFORMAT_1080i = 4, + VIDEOFORMAT_720p = 5, + VIDEOFORMAT_1080p = 6, + VIDEOFORMAT_576p = 7, + } + + public enum TSFrameRate : byte + { + Unknown = 0, + FRAMERATE_23_976 = 1, + FRAMERATE_24 = 2, + FRAMERATE_25 = 3, + FRAMERATE_29_97 = 4, + FRAMERATE_50 = 6, + FRAMERATE_59_94 = 7 + } + + public enum TSChannelLayout : byte + { + Unknown = 0, + CHANNELLAYOUT_MONO = 1, + CHANNELLAYOUT_STEREO = 3, + CHANNELLAYOUT_MULTI = 6, + CHANNELLAYOUT_COMBO = 12 + } + + public enum TSSampleRate : byte + { + Unknown = 0, + SAMPLERATE_48 = 1, + SAMPLERATE_96 = 4, + SAMPLERATE_192 = 5, + SAMPLERATE_48_192 = 12, + SAMPLERATE_48_96 = 14 + } + + public enum TSAspectRatio + { + Unknown = 0, + ASPECT_4_3 = 2, + ASPECT_16_9 = 3, + ASPECT_2_21 = 4 + } + + public class TSDescriptor + { + public byte Name; + public byte[] Value; + + public TSDescriptor(byte name, byte length) + { + Name = name; + Value = new byte[length]; + } + + public TSDescriptor Clone() + { + TSDescriptor descriptor = + new TSDescriptor(Name, (byte)Value.Length); + Value.CopyTo(descriptor.Value, 0); + return descriptor; + } + } + + public abstract class TSStream + { + public TSStream() + { + } + + public override string ToString() + { + return string.Format("{0} ({1})", CodecShortName, PID); + } + + public ushort PID; + public TSStreamType StreamType; + public List Descriptors = null; + public long BitRate = 0; + public long ActiveBitRate = 0; + public bool IsVBR = false; + public bool IsInitialized = false; + public string LanguageName; + public bool IsHidden = false; + + public ulong PayloadBytes = 0; + public ulong PacketCount = 0; + public double PacketSeconds = 0; + public int AngleIndex = 0; + + public ulong PacketSize + { + get + { + return PacketCount * 192; + } + } + + private string _LanguageCode; + public string LanguageCode + { + get + { + return _LanguageCode; + } + set + { + _LanguageCode = value; + LanguageName = LanguageCodes.GetName(value); + } + } + + public bool IsVideoStream + { + get + { + switch (StreamType) + { + case TSStreamType.MPEG1_VIDEO: + case TSStreamType.MPEG2_VIDEO: + case TSStreamType.AVC_VIDEO: + case TSStreamType.MVC_VIDEO: + case TSStreamType.VC1_VIDEO: + return true; + + default: + return false; + } + } + } + + public bool IsAudioStream + { + get + { + switch (StreamType) + { + case TSStreamType.MPEG1_AUDIO: + case TSStreamType.MPEG2_AUDIO: + case TSStreamType.LPCM_AUDIO: + case TSStreamType.AC3_AUDIO: + case TSStreamType.AC3_PLUS_AUDIO: + case TSStreamType.AC3_PLUS_SECONDARY_AUDIO: + case TSStreamType.AC3_TRUE_HD_AUDIO: + case TSStreamType.DTS_AUDIO: + case TSStreamType.DTS_HD_AUDIO: + case TSStreamType.DTS_HD_SECONDARY_AUDIO: + case TSStreamType.DTS_HD_MASTER_AUDIO: + return true; + + default: + return false; + } + } + } + + public bool IsGraphicsStream + { + get + { + switch (StreamType) + { + case TSStreamType.PRESENTATION_GRAPHICS: + case TSStreamType.INTERACTIVE_GRAPHICS: + return true; + + default: + return false; + } + } + } + + public bool IsTextStream + { + get + { + switch (StreamType) + { + case TSStreamType.SUBTITLE: + return true; + + default: + return false; + } + } + } + + public string CodecName + { + get + { + switch (StreamType) + { + case TSStreamType.MPEG1_VIDEO: + return "MPEG-1 Video"; + case TSStreamType.MPEG2_VIDEO: + return "MPEG-2 Video"; + case TSStreamType.AVC_VIDEO: + return "MPEG-4 AVC Video"; + case TSStreamType.MVC_VIDEO: + return "MPEG-4 MVC Video"; + case TSStreamType.VC1_VIDEO: + return "VC-1 Video"; + case TSStreamType.MPEG1_AUDIO: + return "MP1 Audio"; + case TSStreamType.MPEG2_AUDIO: + return "MP2 Audio"; + case TSStreamType.LPCM_AUDIO: + return "LPCM Audio"; + case TSStreamType.AC3_AUDIO: + if (((TSAudioStream)this).AudioMode == TSAudioMode.Extended) + return "Dolby Digital EX Audio"; + else + return "Dolby Digital Audio"; + case TSStreamType.AC3_PLUS_AUDIO: + case TSStreamType.AC3_PLUS_SECONDARY_AUDIO: + return "Dolby Digital Plus Audio"; + case TSStreamType.AC3_TRUE_HD_AUDIO: + return "Dolby TrueHD Audio"; + case TSStreamType.DTS_AUDIO: + if (((TSAudioStream)this).AudioMode == TSAudioMode.Extended) + return "DTS-ES Audio"; + else + return "DTS Audio"; + case TSStreamType.DTS_HD_AUDIO: + return "DTS-HD High-Res Audio"; + case TSStreamType.DTS_HD_SECONDARY_AUDIO: + return "DTS Express"; + case TSStreamType.DTS_HD_MASTER_AUDIO: + return "DTS-HD Master Audio"; + case TSStreamType.PRESENTATION_GRAPHICS: + return "Presentation Graphics"; + case TSStreamType.INTERACTIVE_GRAPHICS: + return "Interactive Graphics"; + case TSStreamType.SUBTITLE: + return "Subtitle"; + default: + return "UNKNOWN"; + } + } + } + + public string CodecAltName + { + get + { + switch (StreamType) + { + case TSStreamType.MPEG1_VIDEO: + return "MPEG-1"; + case TSStreamType.MPEG2_VIDEO: + return "MPEG-2"; + case TSStreamType.AVC_VIDEO: + return "AVC"; + case TSStreamType.MVC_VIDEO: + return "MVC"; + case TSStreamType.VC1_VIDEO: + return "VC-1"; + case TSStreamType.MPEG1_AUDIO: + return "MP1"; + case TSStreamType.MPEG2_AUDIO: + return "MP2"; + case TSStreamType.LPCM_AUDIO: + return "LPCM"; + case TSStreamType.AC3_AUDIO: + return "DD AC3"; + case TSStreamType.AC3_PLUS_AUDIO: + case TSStreamType.AC3_PLUS_SECONDARY_AUDIO: + return "DD AC3+"; + case TSStreamType.AC3_TRUE_HD_AUDIO: + return "Dolby TrueHD"; + case TSStreamType.DTS_AUDIO: + return "DTS"; + case TSStreamType.DTS_HD_AUDIO: + return "DTS-HD Hi-Res"; + case TSStreamType.DTS_HD_SECONDARY_AUDIO: + return "DTS Express"; + case TSStreamType.DTS_HD_MASTER_AUDIO: + return "DTS-HD Master"; + case TSStreamType.PRESENTATION_GRAPHICS: + return "PGS"; + case TSStreamType.INTERACTIVE_GRAPHICS: + return "IGS"; + case TSStreamType.SUBTITLE: + return "SUB"; + default: + return "UNKNOWN"; + } + } + } + + public string CodecShortName + { + get + { + switch (StreamType) + { + case TSStreamType.MPEG1_VIDEO: + return "MPEG-1"; + case TSStreamType.MPEG2_VIDEO: + return "MPEG-2"; + case TSStreamType.AVC_VIDEO: + return "AVC"; + case TSStreamType.MVC_VIDEO: + return "MVC"; + case TSStreamType.VC1_VIDEO: + return "VC-1"; + case TSStreamType.MPEG1_AUDIO: + return "MP1"; + case TSStreamType.MPEG2_AUDIO: + return "MP2"; + case TSStreamType.LPCM_AUDIO: + return "LPCM"; + case TSStreamType.AC3_AUDIO: + if (((TSAudioStream)this).AudioMode == TSAudioMode.Extended) + return "AC3-EX"; + else + return "AC3"; + case TSStreamType.AC3_PLUS_AUDIO: + case TSStreamType.AC3_PLUS_SECONDARY_AUDIO: + return "AC3+"; + case TSStreamType.AC3_TRUE_HD_AUDIO: + return "TrueHD"; + case TSStreamType.DTS_AUDIO: + if (((TSAudioStream)this).AudioMode == TSAudioMode.Extended) + return "DTS-ES"; + else + return "DTS"; + case TSStreamType.DTS_HD_AUDIO: + return "DTS-HD HR"; + case TSStreamType.DTS_HD_SECONDARY_AUDIO: + return "DTS Express"; + case TSStreamType.DTS_HD_MASTER_AUDIO: + return "DTS-HD MA"; + case TSStreamType.PRESENTATION_GRAPHICS: + return "PGS"; + case TSStreamType.INTERACTIVE_GRAPHICS: + return "IGS"; + case TSStreamType.SUBTITLE: + return "SUB"; + default: + return "UNKNOWN"; + } + } + } + + public virtual string Description + { + get + { + return ""; + } + } + + public abstract TSStream Clone(); + + protected void CopyTo(TSStream stream) + { + stream.PID = PID; + stream.StreamType = StreamType; + stream.IsVBR = IsVBR; + stream.BitRate = BitRate; + stream.IsInitialized = IsInitialized; + stream.LanguageCode = _LanguageCode; + if (Descriptors != null) + { + stream.Descriptors = new List(); + foreach (TSDescriptor descriptor in Descriptors) + { + stream.Descriptors.Add(descriptor.Clone()); + } + } + } + } + + public class TSVideoStream : TSStream + { + public TSVideoStream() + { + } + + public int Width; + public int Height; + public bool IsInterlaced; + public int FrameRateEnumerator; + public int FrameRateDenominator; + public TSAspectRatio AspectRatio; + public string EncodingProfile; + + private TSVideoFormat _VideoFormat; + public TSVideoFormat VideoFormat + { + get + { + return _VideoFormat; + } + set + { + _VideoFormat = value; + switch (value) + { + case TSVideoFormat.VIDEOFORMAT_480i: + Height = 480; + IsInterlaced = true; + break; + case TSVideoFormat.VIDEOFORMAT_480p: + Height = 480; + IsInterlaced = false; + break; + case TSVideoFormat.VIDEOFORMAT_576i: + Height = 576; + IsInterlaced = true; + break; + case TSVideoFormat.VIDEOFORMAT_576p: + Height = 576; + IsInterlaced = false; + break; + case TSVideoFormat.VIDEOFORMAT_720p: + Height = 720; + IsInterlaced = false; + break; + case TSVideoFormat.VIDEOFORMAT_1080i: + Height = 1080; + IsInterlaced = true; + break; + case TSVideoFormat.VIDEOFORMAT_1080p: + Height = 1080; + IsInterlaced = false; + break; + } + } + } + + private TSFrameRate _FrameRate; + public TSFrameRate FrameRate + { + get + { + return _FrameRate; + } + set + { + _FrameRate = value; + switch (value) + { + case TSFrameRate.FRAMERATE_23_976: + FrameRateEnumerator = 24000; + FrameRateDenominator = 1001; + break; + case TSFrameRate.FRAMERATE_24: + FrameRateEnumerator = 24000; + FrameRateDenominator = 1000; + break; + case TSFrameRate.FRAMERATE_25: + FrameRateEnumerator = 25000; + FrameRateDenominator = 1000; + break; + case TSFrameRate.FRAMERATE_29_97: + FrameRateEnumerator = 30000; + FrameRateDenominator = 1001; + break; + case TSFrameRate.FRAMERATE_50: + FrameRateEnumerator = 50000; + FrameRateDenominator = 1000; + break; + case TSFrameRate.FRAMERATE_59_94: + FrameRateEnumerator = 60000; + FrameRateDenominator = 1001; + break; + } + } + } + + public override string Description + { + get + { + string description = ""; + + if (Height > 0) + { + description += string.Format("{0:D}{1} / ", + Height, + IsInterlaced ? "i" : "p"); + } + if (FrameRateEnumerator > 0 && + FrameRateDenominator > 0) + { + if (FrameRateEnumerator % FrameRateDenominator == 0) + { + description += string.Format("{0:D} fps / ", + FrameRateEnumerator / FrameRateDenominator); + } + else + { + description += string.Format("{0:F3} fps / ", + (double)FrameRateEnumerator / FrameRateDenominator); + } + + } + if (AspectRatio == TSAspectRatio.ASPECT_4_3) + { + description += "4:3 / "; + } + else if (AspectRatio == TSAspectRatio.ASPECT_16_9) + { + description += "16:9 / "; + } + if (EncodingProfile != null) + { + description += EncodingProfile + " / "; + } + if (description.EndsWith(" / ")) + { + description = description.Substring(0, description.Length - 3); + } + return description; + } + } + + public override TSStream Clone() + { + TSVideoStream stream = new TSVideoStream(); + CopyTo(stream); + + stream.VideoFormat = _VideoFormat; + stream.FrameRate = _FrameRate; + stream.Width = Width; + stream.Height = Height; + stream.IsInterlaced = IsInterlaced; + stream.FrameRateEnumerator = FrameRateEnumerator; + stream.FrameRateDenominator = FrameRateDenominator; + stream.AspectRatio = AspectRatio; + stream.EncodingProfile = EncodingProfile; + + return stream; + } + } + + public enum TSAudioMode + { + Unknown, + DualMono, + Stereo, + Surround, + Extended + } + + public class TSAudioStream : TSStream + { + public TSAudioStream() + { + } + + public int SampleRate; + public int ChannelCount; + public int BitDepth; + public int LFE; + public int DialNorm; + public TSAudioMode AudioMode; + public TSAudioStream CoreStream; + public TSChannelLayout ChannelLayout; + + public static int ConvertSampleRate( + TSSampleRate sampleRate) + { + switch (sampleRate) + { + case TSSampleRate.SAMPLERATE_48: + return 48000; + + case TSSampleRate.SAMPLERATE_96: + case TSSampleRate.SAMPLERATE_48_96: + return 96000; + + case TSSampleRate.SAMPLERATE_192: + case TSSampleRate.SAMPLERATE_48_192: + return 192000; + } + return 0; + } + + public string ChannelDescription + { + get + { + if (ChannelLayout == TSChannelLayout.CHANNELLAYOUT_MONO && + ChannelCount == 2) + { + } + + string description = ""; + if (ChannelCount > 0) + { + description += string.Format( + "{0:D}.{1:D}", + ChannelCount, LFE); + } + else + { + switch (ChannelLayout) + { + case TSChannelLayout.CHANNELLAYOUT_MONO: + description += "1.0"; + break; + case TSChannelLayout.CHANNELLAYOUT_STEREO: + description += "2.0"; + break; + case TSChannelLayout.CHANNELLAYOUT_MULTI: + description += "5.1"; + break; + } + } + if (AudioMode == TSAudioMode.Extended) + { + if (StreamType == TSStreamType.AC3_AUDIO) + { + description += "-EX"; + } + if (StreamType == TSStreamType.DTS_AUDIO || + StreamType == TSStreamType.DTS_HD_AUDIO || + StreamType == TSStreamType.DTS_HD_MASTER_AUDIO) + { + description += "-ES"; + } + } + return description; + } + } + + public override string Description + { + get + { + string description = ChannelDescription; + + if (SampleRate > 0) + { + description += string.Format( + " / {0:D} kHz", SampleRate / 1000); + } + if (BitRate > 0) + { + description += string.Format( + " / {0:D} kbps", (uint)Math.Round((double)BitRate / 1000)); + } + if (BitDepth > 0) + { + description += string.Format( + " / {0:D}-bit", BitDepth); + } + if (DialNorm != 0) + { + description += string.Format( + " / DN {0}dB", DialNorm); + } + if (ChannelCount == 2) + { + switch (AudioMode) + { + case TSAudioMode.DualMono: + description += " / Dual Mono"; + break; + + case TSAudioMode.Surround: + description += " / Dolby Surround"; + break; + } + } + if (description.EndsWith(" / ")) + { + description = description.Substring(0, description.Length - 3); + } + if (CoreStream != null) + { + string codec = ""; + switch (CoreStream.StreamType) + { + case TSStreamType.AC3_AUDIO: + codec = "AC3 Embedded"; + break; + case TSStreamType.DTS_AUDIO: + codec = "DTS Core"; + break; + } + description += string.Format( + " ({0}: {1})", + codec, + CoreStream.Description); + } + return description; + } + } + + public override TSStream Clone() + { + TSAudioStream stream = new TSAudioStream(); + CopyTo(stream); + + stream.SampleRate = SampleRate; + stream.ChannelLayout = ChannelLayout; + stream.ChannelCount = ChannelCount; + stream.BitDepth = BitDepth; + stream.LFE = LFE; + stream.DialNorm = DialNorm; + stream.AudioMode = AudioMode; + if (CoreStream != null) + { + stream.CoreStream = (TSAudioStream)CoreStream.Clone(); + } + + return stream; + } + } + + public class TSGraphicsStream : TSStream + { + public TSGraphicsStream() + { + IsVBR = true; + IsInitialized = true; + } + + public override TSStream Clone() + { + TSGraphicsStream stream = new TSGraphicsStream(); + CopyTo(stream); + return stream; + } + } + + public class TSTextStream : TSStream + { + public TSTextStream() + { + IsVBR = true; + IsInitialized = true; + } + + public override TSStream Clone() + { + TSTextStream stream = new TSTextStream(); + CopyTo(stream); + return stream; + } + } +} diff --git a/BDInfo/TSStreamBuffer.cs b/BDInfo/TSStreamBuffer.cs new file mode 100644 index 0000000000..78967ae34e --- /dev/null +++ b/BDInfo/TSStreamBuffer.cs @@ -0,0 +1,145 @@ +//============================================================================ +// BDInfo - Blu-ray Video and Audio Analysis Tool +// Copyright © 2010 Cinema Squid +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//============================================================================= + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace BDInfo +{ + public class TSStreamBuffer + { + private MemoryStream Stream = new MemoryStream(); + private int SkipBits = 0; + private byte[] Buffer; + private int BufferLength = 0; + public int TransferLength = 0; + + public TSStreamBuffer() + { + Buffer = new byte[4096]; + Stream = new MemoryStream(Buffer); + } + + public long Length + { + get + { + return (long)BufferLength; + } + } + + public long Position + { + get + { + return Stream.Position; + } + } + + public void Add( + byte[] buffer, + int offset, + int length) + { + TransferLength += length; + + if (BufferLength + length >= Buffer.Length) + { + length = Buffer.Length - BufferLength; + } + if (length > 0) + { + Array.Copy(buffer, offset, Buffer, BufferLength, length); + BufferLength += length; + } + } + + public void Seek( + long offset, + SeekOrigin loc) + { + Stream.Seek(offset, loc); + } + + public void Reset() + { + BufferLength = 0; + TransferLength = 0; + } + + public void BeginRead() + { + SkipBits = 0; + Stream.Seek(0, SeekOrigin.Begin); + } + + public void EndRead() + { + } + + public byte[] ReadBytes(int bytes) + { + if (Stream.Position + bytes >= BufferLength) + { + return null; + } + + byte[] value = new byte[bytes]; + Stream.Read(value, 0, bytes); + return value; + } + + public byte ReadByte() + { + return (byte)Stream.ReadByte(); + } + + public int ReadBits(int bits) + { + long pos = Stream.Position; + + int shift = 24; + int data = 0; + for (int i = 0; i < 4; i++) + { + if (pos + i >= BufferLength) break; + data += (Stream.ReadByte() << shift); + shift -= 8; + } + BitVector32 vector = new BitVector32(data); + + int value = 0; + for (int i = SkipBits; i < SkipBits + bits; i++) + { + value <<= 1; + value += (vector[1 << (32 - i - 1)] ? 1 : 0); + } + + SkipBits += bits; + Stream.Seek(pos + (SkipBits >> 3), SeekOrigin.Begin); + SkipBits = SkipBits % 8; + + return value; + } + } +} diff --git a/BDInfo/TSStreamClip.cs b/BDInfo/TSStreamClip.cs new file mode 100644 index 0000000000..ba3c96e82a --- /dev/null +++ b/BDInfo/TSStreamClip.cs @@ -0,0 +1,114 @@ +//============================================================================ +// BDInfo - Blu-ray Video and Audio Analysis Tool +// Copyright © 2010 Cinema Squid +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//============================================================================= + +using System; +using System.Collections.Generic; +using System.Text; + +namespace BDInfo +{ + public class TSStreamClip + { + public int AngleIndex = 0; + public string Name; + public double TimeIn; + public double TimeOut; + public double RelativeTimeIn; + public double RelativeTimeOut; + public double Length; + + public ulong FileSize = 0; + public ulong InterleavedFileSize = 0; + public ulong PayloadBytes = 0; + public ulong PacketCount = 0; + public double PacketSeconds = 0; + + public List Chapters = new List(); + + public TSStreamFile StreamFile = null; + public TSStreamClipFile StreamClipFile = null; + + public TSStreamClip( + TSStreamFile streamFile, + TSStreamClipFile streamClipFile) + { + if (streamFile != null) + { + Name = streamFile.Name; + StreamFile = streamFile; + FileSize = (ulong)StreamFile.FileInfo.Length; + if (StreamFile.InterleavedFile != null) + { + InterleavedFileSize = (ulong)StreamFile.InterleavedFile.FileInfo.Length; + } + } + StreamClipFile = streamClipFile; + } + + public string DisplayName + { + get + { + if (StreamFile != null && + StreamFile.InterleavedFile != null && + BDInfoSettings.EnableSSIF) + { + return StreamFile.InterleavedFile.Name; + } + return Name; + } + } + + public ulong PacketSize + { + get + { + return PacketCount * 192; + } + } + + public ulong PacketBitRate + { + get + { + if (PacketSeconds > 0) + { + return (ulong)Math.Round(((PacketSize * 8.0) / PacketSeconds)); + } + return 0; + } + } + + public bool IsCompatible(TSStreamClip clip) + { + foreach (TSStream stream1 in StreamFile.Streams.Values) + { + if (clip.StreamFile.Streams.ContainsKey(stream1.PID)) + { + TSStream stream2 = clip.StreamFile.Streams[stream1.PID]; + if (stream1.StreamType != stream2.StreamType) + { + return false; + } + } + } + return true; + } + } +} diff --git a/BDInfo/TSStreamClipFile.cs b/BDInfo/TSStreamClipFile.cs new file mode 100644 index 0000000000..5042dcde1e --- /dev/null +++ b/BDInfo/TSStreamClipFile.cs @@ -0,0 +1,248 @@ +//============================================================================ +// BDInfo - Blu-ray Video and Audio Analysis Tool +// Copyright © 2010 Cinema Squid +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//============================================================================= + +#undef DEBUG +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace BDInfo +{ + public class TSStreamClipFile + { + public FileInfo FileInfo = null; + public string FileType = null; + public bool IsValid = false; + public string Name = null; + + public Dictionary Streams = + new Dictionary(); + + public TSStreamClipFile( + FileInfo fileInfo) + { + FileInfo = fileInfo; + Name = fileInfo.Name.ToUpper(); + } + + public void Scan() + { + FileStream fileStream = null; + BinaryReader fileReader = null; + + try + { +#if DEBUG + Debug.WriteLine(string.Format( + "Scanning {0}...", Name)); +#endif + Streams.Clear(); + + fileStream = File.OpenRead(FileInfo.FullName); + fileReader = new BinaryReader(fileStream); + + byte[] data = new byte[fileStream.Length]; + fileReader.Read(data, 0, data.Length); + + byte[] fileType = new byte[8]; + Array.Copy(data, 0, fileType, 0, fileType.Length); + + FileType = ASCIIEncoding.ASCII.GetString(fileType); + if (FileType != "HDMV0100" && + FileType != "HDMV0200") + { + throw new Exception(string.Format( + "Clip info file {0} has an unknown file type {1}.", + FileInfo.Name, FileType)); + } +#if DEBUG + Debug.WriteLine(string.Format( + "\tFileType: {0}", FileType)); +#endif + int clipIndex = + ((int)data[12] << 24) + + ((int)data[13] << 16) + + ((int)data[14] << 8) + + ((int)data[15]); + + int clipLength = + ((int)data[clipIndex] << 24) + + ((int)data[clipIndex + 1] << 16) + + ((int)data[clipIndex + 2] << 8) + + ((int)data[clipIndex + 3]); + + byte[] clipData = new byte[clipLength]; + Array.Copy(data, clipIndex + 4, clipData, 0, clipData.Length); + + int streamCount = clipData[8]; +#if DEBUG + Debug.WriteLine(string.Format( + "\tStreamCount: {0}", streamCount)); +#endif + int streamOffset = 10; + for (int streamIndex = 0; + streamIndex < streamCount; + streamIndex++) + { + TSStream stream = null; + + ushort PID = (ushort) + ((clipData[streamOffset] << 8) + + clipData[streamOffset + 1]); + + streamOffset += 2; + + TSStreamType streamType = (TSStreamType) + clipData[streamOffset + 1]; + switch (streamType) + { + case TSStreamType.MVC_VIDEO: + // TODO + break; + + case TSStreamType.AVC_VIDEO: + case TSStreamType.MPEG1_VIDEO: + case TSStreamType.MPEG2_VIDEO: + case TSStreamType.VC1_VIDEO: + { + TSVideoFormat videoFormat = (TSVideoFormat) + (clipData[streamOffset + 2] >> 4); + TSFrameRate frameRate = (TSFrameRate) + (clipData[streamOffset + 2] & 0xF); + TSAspectRatio aspectRatio = (TSAspectRatio) + (clipData[streamOffset + 3] >> 4); + + stream = new TSVideoStream(); + ((TSVideoStream)stream).VideoFormat = videoFormat; + ((TSVideoStream)stream).AspectRatio = aspectRatio; + ((TSVideoStream)stream).FrameRate = frameRate; +#if DEBUG + Debug.WriteLine(string.Format( + "\t{0} {1} {2} {3} {4}", + PID, + streamType, + videoFormat, + frameRate, + aspectRatio)); +#endif + } + break; + + case TSStreamType.AC3_AUDIO: + case TSStreamType.AC3_PLUS_AUDIO: + case TSStreamType.AC3_PLUS_SECONDARY_AUDIO: + case TSStreamType.AC3_TRUE_HD_AUDIO: + case TSStreamType.DTS_AUDIO: + case TSStreamType.DTS_HD_AUDIO: + case TSStreamType.DTS_HD_MASTER_AUDIO: + case TSStreamType.DTS_HD_SECONDARY_AUDIO: + case TSStreamType.LPCM_AUDIO: + case TSStreamType.MPEG1_AUDIO: + case TSStreamType.MPEG2_AUDIO: + { + byte[] languageBytes = new byte[3]; + Array.Copy(clipData, streamOffset + 3, + languageBytes, 0, languageBytes.Length); + string languageCode = + ASCIIEncoding.ASCII.GetString(languageBytes); + + TSChannelLayout channelLayout = (TSChannelLayout) + (clipData[streamOffset + 2] >> 4); + TSSampleRate sampleRate = (TSSampleRate) + (clipData[streamOffset + 2] & 0xF); + + stream = new TSAudioStream(); + ((TSAudioStream)stream).LanguageCode = languageCode; + ((TSAudioStream)stream).ChannelLayout = channelLayout; + ((TSAudioStream)stream).SampleRate = TSAudioStream.ConvertSampleRate(sampleRate); + ((TSAudioStream)stream).LanguageCode = languageCode; +#if DEBUG + Debug.WriteLine(string.Format( + "\t{0} {1} {2} {3} {4}", + PID, + streamType, + languageCode, + channelLayout, + sampleRate)); +#endif + } + break; + + case TSStreamType.INTERACTIVE_GRAPHICS: + case TSStreamType.PRESENTATION_GRAPHICS: + { + byte[] languageBytes = new byte[3]; + Array.Copy(clipData, streamOffset + 2, + languageBytes, 0, languageBytes.Length); + string languageCode = + ASCIIEncoding.ASCII.GetString(languageBytes); + + stream = new TSGraphicsStream(); + stream.LanguageCode = languageCode; +#if DEBUG + Debug.WriteLine(string.Format( + "\t{0} {1} {2}", + PID, + streamType, + languageCode)); +#endif + } + break; + + case TSStreamType.SUBTITLE: + { + byte[] languageBytes = new byte[3]; + Array.Copy(clipData, streamOffset + 3, + languageBytes, 0, languageBytes.Length); + string languageCode = + ASCIIEncoding.ASCII.GetString(languageBytes); +#if DEBUG + Debug.WriteLine(string.Format( + "\t{0} {1} {2}", + PID, + streamType, + languageCode)); +#endif + stream = new TSTextStream(); + stream.LanguageCode = languageCode; + } + break; + } + + if (stream != null) + { + stream.PID = PID; + stream.StreamType = streamType; + Streams.Add(PID, stream); + } + + streamOffset += clipData[streamOffset] + 1; + } + IsValid = true; + } + finally + { + if (fileReader != null) fileReader.Close(); + if (fileStream != null) fileStream.Close(); + } + } + } +} diff --git a/BDInfo/TSStreamFile.cs b/BDInfo/TSStreamFile.cs new file mode 100644 index 0000000000..4b3a759624 --- /dev/null +++ b/BDInfo/TSStreamFile.cs @@ -0,0 +1,1551 @@ +//============================================================================ +// BDInfo - Blu-ray Video and Audio Analysis Tool +// Copyright © 2010 Cinema Squid +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//============================================================================= + +#undef DEBUG +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace BDInfo +{ + public class TSStreamState + { + public ulong TransferCount = 0; + + public string StreamTag = null; + + public ulong TotalPackets = 0; + public ulong WindowPackets = 0; + + public ulong TotalBytes = 0; + public ulong WindowBytes = 0; + + public long PeakTransferLength = 0; + public long PeakTransferRate = 0; + + public double TransferMarker = 0; + public double TransferInterval = 0; + + public TSStreamBuffer StreamBuffer = new TSStreamBuffer(); + + public uint Parse = 0; + public bool TransferState = false; + public int TransferLength = 0; + public int PacketLength = 0; + public byte PacketLengthParse = 0; + public byte PacketParse = 0; + + public byte PTSParse = 0; + public ulong PTS = 0; + public ulong PTSTemp = 0; + public ulong PTSLast = 0; + public ulong PTSPrev = 0; + public ulong PTSDiff = 0; + public ulong PTSCount = 0; + public ulong PTSTransfer = 0; + + public byte DTSParse = 0; + public ulong DTSTemp = 0; + public ulong DTSPrev = 0; + + public byte PESHeaderLength = 0; + public byte PESHeaderFlags = 0; +#if DEBUG + public byte PESHeaderIndex = 0; + public byte[] PESHeader = new byte[256 + 9]; +#endif + } + + public class TSPacketParser + { + public bool SyncState = false; + public byte TimeCodeParse = 4; + public byte PacketLength = 0; + public byte HeaderParse = 0; + + public uint TimeCode; + public byte TransportErrorIndicator; + public byte PayloadUnitStartIndicator; + public byte TransportPriority; + public ushort PID; + public byte TransportScramblingControl; + public byte AdaptionFieldControl; + + public bool AdaptionFieldState = false; + public byte AdaptionFieldParse = 0; + public byte AdaptionFieldLength = 0; + + public ushort PCRPID = 0xFFFF; + public byte PCRParse = 0; + public ulong PreviousPCR = 0; + public ulong PCR = 0; + public ulong PCRCount = 0; + public ulong PTSFirst = ulong.MaxValue; + public ulong PTSLast = ulong.MinValue; + public ulong PTSDiff = 0; + + public byte[] PAT = new byte[1024]; + public bool PATSectionStart = false; + public byte PATPointerField = 0; + public uint PATOffset = 0; + public byte PATSectionLengthParse = 0; + public ushort PATSectionLength = 0; + public uint PATSectionParse = 0; + public bool PATTransferState = false; + public byte PATSectionNumber = 0; + public byte PATLastSectionNumber = 0; + + public ushort TransportStreamId = 0xFFFF; + + public List PMTProgramDescriptors = new List(); + public ushort PMTPID = 0xFFFF; + public Dictionary PMT = new Dictionary(); + public bool PMTSectionStart = false; + public ushort PMTProgramInfoLength = 0; + public byte PMTProgramDescriptor = 0; + public byte PMTProgramDescriptorLengthParse = 0; + public byte PMTProgramDescriptorLength = 0; + public ushort PMTStreamInfoLength = 0; + public uint PMTStreamDescriptorLengthParse = 0; + public uint PMTStreamDescriptorLength = 0; + public byte PMTPointerField = 0; + public uint PMTOffset = 0; + public uint PMTSectionLengthParse = 0; + public ushort PMTSectionLength = 0; + public uint PMTSectionParse = 0; + public bool PMTTransferState = false; + public byte PMTSectionNumber = 0; + public byte PMTLastSectionNumber = 0; + + public byte PMTTemp = 0; + + public TSStream Stream = null; + public TSStreamState StreamState = null; + + public ulong TotalPackets = 0; + } + + public class TSStreamDiagnostics + { + public ulong Bytes = 0; + public ulong Packets = 0; + public double Marker = 0; + public double Interval = 0; + public string Tag = null; + } + + public class TSStreamFile + { + public FileInfo FileInfo = null; + public string Name = null; + public long Size = 0; + public double Length = 0; + + public TSInterleavedFile InterleavedFile = null; + + private Dictionary StreamStates = + new Dictionary(); + + public Dictionary Streams = + new Dictionary(); + + public Dictionary> StreamDiagnostics = + new Dictionary>(); + + private List Playlists = null; + + public TSStreamFile(FileInfo fileInfo) + { + FileInfo = fileInfo; + Name = fileInfo.Name.ToUpper(); + } + + public string DisplayName + { + get + { + if (BDInfoSettings.EnableSSIF && + InterleavedFile != null) + { + return InterleavedFile.Name; + } + return Name; + } + } + + private bool ScanStream( + TSStream stream, + TSStreamState streamState, + TSStreamBuffer buffer) + { + streamState.StreamTag = null; + + long bitrate = 0; + if (stream.IsAudioStream && + streamState.PTSTransfer > 0) + { + bitrate = (long)Math.Round( + (buffer.TransferLength * 8.0) / + ((double)streamState.PTSTransfer / 90000)); + + if (bitrate > streamState.PeakTransferRate) + { + streamState.PeakTransferRate = bitrate; + } + } + if (buffer.TransferLength > streamState.PeakTransferLength) + { + streamState.PeakTransferLength = buffer.TransferLength; + } + + buffer.BeginRead(); + switch (stream.StreamType) + { + case TSStreamType.MPEG2_VIDEO: + TSCodecMPEG2.Scan( + (TSVideoStream)stream, buffer, ref streamState.StreamTag); + break; + + case TSStreamType.AVC_VIDEO: + TSCodecAVC.Scan( + (TSVideoStream)stream, buffer, ref streamState.StreamTag); + break; + + case TSStreamType.MVC_VIDEO: + TSCodecMVC.Scan( + (TSVideoStream)stream, buffer, ref streamState.StreamTag); + break; + + case TSStreamType.VC1_VIDEO: + TSCodecVC1.Scan( + (TSVideoStream)stream, buffer, ref streamState.StreamTag); + break; + + case TSStreamType.AC3_AUDIO: + TSCodecAC3.Scan( + (TSAudioStream)stream, buffer, ref streamState.StreamTag); + break; + + case TSStreamType.AC3_PLUS_AUDIO: + case TSStreamType.AC3_PLUS_SECONDARY_AUDIO: + TSCodecAC3.Scan( + (TSAudioStream)stream, buffer, ref streamState.StreamTag); + break; + + case TSStreamType.AC3_TRUE_HD_AUDIO: + TSCodecTrueHD.Scan( + (TSAudioStream)stream, buffer, ref streamState.StreamTag); + break; + + case TSStreamType.LPCM_AUDIO: + TSCodecLPCM.Scan( + (TSAudioStream)stream, buffer, ref streamState.StreamTag); + break; + + case TSStreamType.DTS_AUDIO: + TSCodecDTS.Scan( + (TSAudioStream)stream, buffer, bitrate, ref streamState.StreamTag); + break; + + case TSStreamType.DTS_HD_AUDIO: + case TSStreamType.DTS_HD_MASTER_AUDIO: + case TSStreamType.DTS_HD_SECONDARY_AUDIO: + TSCodecDTSHD.Scan( + (TSAudioStream)stream, buffer, bitrate, ref streamState.StreamTag); + break; + + default: + stream.IsInitialized = true; + break; + } + buffer.EndRead(); + streamState.StreamBuffer.Reset(); + + bool isAVC = false; + bool isMVC = false; + foreach (TSStream finishedStream in Streams.Values) + { + if (!finishedStream.IsInitialized) + { + return false; + } + if (finishedStream.StreamType == TSStreamType.AVC_VIDEO) + { + isAVC = true; + } + if (finishedStream.StreamType == TSStreamType.MVC_VIDEO) + { + isMVC = true; + } + } + if (isMVC && !isAVC) + { + return false; + } + return true; + } + + private void UpdateStreamBitrates( + ushort PTSPID, + ulong PTS, + ulong PTSDiff) + { + if (Playlists == null) return; + + foreach (ushort PID in StreamStates.Keys) + { + if (Streams.ContainsKey(PID) && + Streams[PID].IsVideoStream && + PID != PTSPID) + { + continue; + } + if (StreamStates[PID].WindowPackets == 0) + { + continue; + } + UpdateStreamBitrate(PID, PTSPID, PTS, PTSDiff); + } + + foreach (TSPlaylistFile playlist in Playlists) + { + double packetSeconds = 0; + foreach (TSStreamClip clip in playlist.StreamClips) + { + if (clip.AngleIndex == 0) + { + packetSeconds += clip.PacketSeconds; + } + } + if (packetSeconds > 0) + { + foreach (TSStream playlistStream in playlist.SortedStreams) + { + if (playlistStream.IsVBR) + { + playlistStream.BitRate = (long)Math.Round( + ((playlistStream.PayloadBytes * 8.0) / packetSeconds)); + + if (playlistStream.StreamType == TSStreamType.AC3_TRUE_HD_AUDIO && + ((TSAudioStream)playlistStream).CoreStream != null) + { + playlistStream.BitRate -= + ((TSAudioStream)playlistStream).CoreStream.BitRate; + } + } + } + } + } + } + + private void UpdateStreamBitrate( + ushort PID, + ushort PTSPID, + ulong PTS, + ulong PTSDiff) + { + if (Playlists == null) return; + + TSStreamState streamState = StreamStates[PID]; + double streamTime = (double)PTS / 90000; + double streamInterval = (double)PTSDiff / 90000; + double streamOffset = streamTime + streamInterval; + + foreach (TSPlaylistFile playlist in Playlists) + { + foreach (TSStreamClip clip in playlist.StreamClips) + { + if (clip.Name != this.Name) continue; + + if (streamTime == 0 || + (streamTime >= clip.TimeIn && + streamTime <= clip.TimeOut)) + { + clip.PayloadBytes += streamState.WindowBytes; + clip.PacketCount += streamState.WindowPackets; + + if (streamOffset > clip.TimeIn && + streamOffset - clip.TimeIn > clip.PacketSeconds) + { + clip.PacketSeconds = streamOffset - clip.TimeIn; + } + + Dictionary playlistStreams = playlist.Streams; + if (clip.AngleIndex > 0 && + clip.AngleIndex < playlist.AngleStreams.Count + 1) + { + playlistStreams = playlist.AngleStreams[clip.AngleIndex - 1]; + } + if (playlistStreams.ContainsKey(PID)) + { + TSStream stream = playlistStreams[PID]; + + stream.PayloadBytes += streamState.WindowBytes; + stream.PacketCount += streamState.WindowPackets; + + if (stream.IsVideoStream) + { + stream.PacketSeconds += streamInterval; + + stream.ActiveBitRate = (long)Math.Round( + ((stream.PayloadBytes * 8.0) / + stream.PacketSeconds)); + } + + if (stream.StreamType == TSStreamType.AC3_TRUE_HD_AUDIO && + ((TSAudioStream)stream).CoreStream != null) + { + stream.ActiveBitRate -= + ((TSAudioStream)stream).CoreStream.BitRate; + } + } + } + } + } + + if (Streams.ContainsKey(PID)) + { + TSStream stream = Streams[PID]; + stream.PayloadBytes += streamState.WindowBytes; + stream.PacketCount += streamState.WindowPackets; + + if (stream.IsVideoStream) + { + TSStreamDiagnostics diag = new TSStreamDiagnostics(); + diag.Marker = (double)PTS / 90000; + diag.Interval = (double)PTSDiff / 90000; + diag.Bytes = streamState.WindowBytes; + diag.Packets = streamState.WindowPackets; + diag.Tag = streamState.StreamTag; + StreamDiagnostics[PID].Add(diag); + + stream.PacketSeconds += streamInterval; + } + } + streamState.WindowPackets = 0; + streamState.WindowBytes = 0; + } + + public void Scan(List playlists, bool isFullScan) + { + if (playlists == null || playlists.Count == 0) + { + return; + } + + Playlists = playlists; + int dataSize = 16384; + FileStream fileStream = null; + try + { + string fileName; + if (BDInfoSettings.EnableSSIF && + InterleavedFile != null) + { + fileName = InterleavedFile.FileInfo.FullName; + } + else + { + fileName = FileInfo.FullName; + } + fileStream = new FileStream( + fileName, + FileMode.Open, + FileAccess.Read, + FileShare.Read, + dataSize, false); + + Size = 0; + Length = 0; + + Streams.Clear(); + StreamStates.Clear(); + StreamDiagnostics.Clear(); + + TSPacketParser parser = + new TSPacketParser(); + + long fileLength = (uint)fileStream.Length; + byte[] buffer = new byte[dataSize]; + int bufferLength = 0; + while ((bufferLength = + fileStream.Read(buffer, 0, buffer.Length)) > 0) + { + int offset = 0; + for (int i = 0; i < bufferLength; i++) + { + if (parser.SyncState == false) + { + if (parser.TimeCodeParse > 0) + { + parser.TimeCodeParse--; + switch (parser.TimeCodeParse) + { + case 3: + parser.TimeCode = 0; + parser.TimeCode |= + ((uint)buffer[i] & 0x3F) << 24; + break; + case 2: + parser.TimeCode |= + ((uint)buffer[i] & 0xFF) << 16; + break; + case 1: + parser.TimeCode |= + ((uint)buffer[i] & 0xFF) << 8; + break; + case 0: + parser.TimeCode |= + ((uint)buffer[i] & 0xFF); + break; + } + } + else if (buffer[i] == 0x47) + { + parser.SyncState = true; + parser.PacketLength = 187; + parser.TimeCodeParse = 4; + parser.HeaderParse = 3; + } + } + else if (parser.HeaderParse > 0) + { + parser.PacketLength--; + parser.HeaderParse--; + + switch (parser.HeaderParse) + { + case 2: + { + parser.TransportErrorIndicator = + (byte)((buffer[i] >> 7) & 0x1); + parser.PayloadUnitStartIndicator = + (byte)((buffer[i] >> 6) & 0x1); + parser.TransportPriority = + (byte)((buffer[i] >> 5) & 0x1); + parser.PID = + (ushort)((buffer[i] & 0x1f) << 8); + } + break; + + case 1: + { + parser.PID |= (ushort)buffer[i]; + if (Streams.ContainsKey(parser.PID)) + { + parser.Stream = Streams[parser.PID]; + } + else + { + parser.Stream = null; + } + if (!StreamStates.ContainsKey(parser.PID)) + { + StreamStates[parser.PID] = new TSStreamState(); + } + parser.StreamState = StreamStates[parser.PID]; + parser.StreamState.TotalPackets++; + parser.StreamState.WindowPackets++; + parser.TotalPackets++; + } + break; + + case 0: + { + parser.TransportScramblingControl = + (byte)((buffer[i] >> 6) & 0x3); + parser.AdaptionFieldControl = + (byte)((buffer[i] >> 4) & 0x3); + + if ((parser.AdaptionFieldControl & 0x2) == 0x2) + { + parser.AdaptionFieldState = true; + } + if (parser.PayloadUnitStartIndicator == 1) + { + if (parser.PID == 0) + { + parser.PATSectionStart = true; + } + else if (parser.PID == parser.PMTPID) + { + parser.PMTSectionStart = true; + } + else if (parser.StreamState != null && + parser.StreamState.TransferState) + { + parser.StreamState.TransferState = false; + parser.StreamState.TransferCount++; + + bool isFinished = ScanStream( + parser.Stream, + parser.StreamState, + parser.StreamState.StreamBuffer); + + if (!isFullScan && isFinished) + { + return; + } + } + } + } + break; + } + } + else if (parser.AdaptionFieldState) + { + parser.PacketLength--; + parser.AdaptionFieldParse = buffer[i]; + parser.AdaptionFieldLength = buffer[i]; + parser.AdaptionFieldState = false; + } + else if (parser.AdaptionFieldParse > 0) + { + parser.PacketLength--; + parser.AdaptionFieldParse--; + if ((parser.AdaptionFieldLength - parser.AdaptionFieldParse) == 1) + { + if ((buffer[i] & 0x10) == 0x10) + { + parser.PCRParse = 6; + parser.PCR = 0; + } + } + else if (parser.PCRParse > 0) + { + parser.PCRParse--; + parser.PCR = (parser.PCR << 8) + (ulong)buffer[i]; + if (parser.PCRParse == 0) + { + parser.PreviousPCR = parser.PCR; + parser.PCR = (parser.PCR & 0x1FF) + + ((parser.PCR >> 15) * 300); + } + parser.PCRCount++; + } + if (parser.PacketLength == 0) + { + parser.SyncState = false; + } + } + else if (parser.PID == 0) + { + if (parser.PATTransferState) + { + if ((bufferLength - i) > parser.PATSectionLength) + { + offset = parser.PATSectionLength; + } + else + { + offset = (bufferLength - i); + } + if (parser.PacketLength <= offset) + { + offset = parser.PacketLength; + } + + for (int k = 0; k < offset; k++) + { + parser.PAT[parser.PATOffset++] = buffer[i++]; + parser.PATSectionLength--; + parser.PacketLength--; + } --i; + + if (parser.PATSectionLength == 0) + { + parser.PATTransferState = false; + if (parser.PATSectionNumber == parser.PATLastSectionNumber) + { + for (int k = 0; k < (parser.PATOffset - 4); k += 4) + { + uint programNumber = (uint) + ((parser.PAT[k] << 8) + + parser.PAT[k + 1]); + + ushort programPID = (ushort) + (((parser.PAT[k + 2] & 0x1F) << 8) + + parser.PAT[k + 3]); + + if (programNumber == 1) + { + parser.PMTPID = programPID; + } + } + } + } + } + else + { + --parser.PacketLength; + if (parser.PATSectionStart) + { + parser.PATPointerField = buffer[i]; + if (parser.PATPointerField == 0) + { + parser.PATSectionLengthParse = 3; + } + parser.PATSectionStart = false; + } + else if (parser.PATPointerField > 0) + { + --parser.PATPointerField; + if (parser.PATPointerField == 0) + { + parser.PATSectionLengthParse = 3; + } + } + else if (parser.PATSectionLengthParse > 0) + { + --parser.PATSectionLengthParse; + switch (parser.PATSectionLengthParse) + { + case 2: + break; + case 1: + parser.PATSectionLength = (ushort) + ((buffer[i] & 0xF) << 8); + break; + case 0: + parser.PATSectionLength |= buffer[i]; + if (parser.PATSectionLength > 1021) + { + parser.PATSectionLength = 0; + } + else + { + parser.PATSectionParse = 5; + } + break; + } + } + else if (parser.PATSectionParse > 0) + { + --parser.PATSectionLength; + --parser.PATSectionParse; + + switch (parser.PATSectionParse) + { + case 4: + parser.TransportStreamId = (ushort) + (buffer[i] << 8); + break; + case 3: + parser.TransportStreamId |= buffer[i]; + break; + case 2: + break; + case 1: + parser.PATSectionNumber = buffer[i]; + if (parser.PATSectionNumber == 0) + { + parser.PATOffset = 0; + } + break; + case 0: + parser.PATLastSectionNumber = buffer[i]; + parser.PATTransferState = true; + break; + } + } + } + if (parser.PacketLength == 0) + { + parser.SyncState = false; + } + } + else if (parser.PID == parser.PMTPID) + { + if (parser.PMTTransferState) + { + if ((bufferLength - i) >= parser.PMTSectionLength) + { + offset = parser.PMTSectionLength; + } + else + { + offset = (bufferLength - i); + } + if (parser.PacketLength <= offset) + { + offset = parser.PacketLength; + } + if (!parser.PMT.ContainsKey(parser.PID)) + { + parser.PMT[parser.PID] = new byte[1024]; + } + + byte[] PMT = parser.PMT[parser.PID]; + for (int k = 0; k < offset; k++) + { + PMT[parser.PMTOffset++] = buffer[i++]; + --parser.PMTSectionLength; + --parser.PacketLength; + } --i; + + if (parser.PMTSectionLength == 0) + { + parser.PMTTransferState = false; + if (parser.PMTSectionNumber == parser.PMTLastSectionNumber) + { + //Console.WriteLine("PMT Start: " + parser.PMTTemp); + try + { + for (int k = 0; k < (parser.PMTOffset - 4); k += 5) + { + byte streamType = PMT[k]; + + ushort streamPID = (ushort) + (((PMT[k + 1] & 0x1F) << 8) + + PMT[k + 2]); + + ushort streamInfoLength = (ushort) + (((PMT[k + 3] & 0xF) << 8) + + PMT[k + 4]); + + /* + if (streamInfoLength == 2) + { + // TODO: Cleanup + //streamInfoLength = 0; + } + + Console.WriteLine(string.Format( + "Type: {0} PID: {1} Length: {2}", + streamType, streamPID, streamInfoLength)); + */ + + if (!Streams.ContainsKey(streamPID)) + { + List streamDescriptors = + new List(); + + /* + * TODO: Getting bad streamInfoLength + if (streamInfoLength > 0) + { + for (int d = 0; d < streamInfoLength; d++) + { + byte name = PMT[k + d + 5]; + byte length = PMT[k + d + 6]; + TSDescriptor descriptor = + new TSDescriptor(name, length); + for (int v = 0; v < length; v++) + { + descriptor.Value[v] = + PMT[k + d + v + 7]; + } + streamDescriptors.Add(descriptor); + d += (length + 1); + } + } + */ + CreateStream(streamPID, streamType, streamDescriptors); + } + k += streamInfoLength; + } + } + catch (Exception ex) + { + // TODO + Console.WriteLine(ex.Message); + } + } + } + } + else + { + --parser.PacketLength; + if (parser.PMTSectionStart) + { + parser.PMTPointerField = buffer[i]; + if (parser.PMTPointerField == 0) + { + parser.PMTSectionLengthParse = 3; + } + parser.PMTSectionStart = false; + } + else if (parser.PMTPointerField > 0) + { + --parser.PMTPointerField; + if (parser.PMTPointerField == 0) + { + parser.PMTSectionLengthParse = 3; + } + } + else if (parser.PMTSectionLengthParse > 0) + { + --parser.PMTSectionLengthParse; + switch (parser.PMTSectionLengthParse) + { + case 2: + if (buffer[i] != 0x2) + { + parser.PMTSectionLengthParse = 0; + } + break; + case 1: + parser.PMTSectionLength = (ushort) + ((buffer[i] & 0xF) << 8); + break; + case 0: + parser.PMTSectionLength |= buffer[i]; + if (parser.PMTSectionLength > 1021) + { + parser.PMTSectionLength = 0; + } + else + { + parser.PMTSectionParse = 9; + } + break; + } + } + else if (parser.PMTSectionParse > 0) + { + --parser.PMTSectionLength; + --parser.PMTSectionParse; + + switch (parser.PMTSectionParse) + { + case 8: + case 7: + break; + case 6: + parser.PMTTemp = buffer[i]; + break; + case 5: + parser.PMTSectionNumber = buffer[i]; + if (parser.PMTSectionNumber == 0) + { + parser.PMTOffset = 0; + } + break; + case 4: + parser.PMTLastSectionNumber = buffer[i]; + break; + case 3: + parser.PCRPID = (ushort) + ((buffer[i] & 0x1F) << 8); + break; + case 2: + parser.PCRPID |= buffer[i]; + break; + case 1: + parser.PMTProgramInfoLength = (ushort) + ((buffer[i] & 0xF) << 8); + break; + case 0: + parser.PMTProgramInfoLength |= buffer[i]; + if (parser.PMTProgramInfoLength == 0) + { + parser.PMTTransferState = true; + } + else + { + parser.PMTProgramDescriptorLengthParse = 2; + } + break; + } + } + else if (parser.PMTProgramInfoLength > 0) + { + --parser.PMTSectionLength; + --parser.PMTProgramInfoLength; + + if (parser.PMTProgramDescriptorLengthParse > 0) + { + --parser.PMTProgramDescriptorLengthParse; + switch (parser.PMTProgramDescriptorLengthParse) + { + case 1: + parser.PMTProgramDescriptor = buffer[i]; + break; + case 0: + parser.PMTProgramDescriptorLength = buffer[i]; + parser.PMTProgramDescriptors.Add( + new TSDescriptor( + parser.PMTProgramDescriptor, + parser.PMTProgramDescriptorLength)); + break; + } + } + else if (parser.PMTProgramDescriptorLength > 0) + { + --parser.PMTProgramDescriptorLength; + + TSDescriptor descriptor = parser.PMTProgramDescriptors[ + parser.PMTProgramDescriptors.Count - 1]; + + int valueIndex = + descriptor.Value.Length - + parser.PMTProgramDescriptorLength - 1; + + descriptor.Value[valueIndex] = buffer[i]; + + if (parser.PMTProgramDescriptorLength == 0 && + parser.PMTProgramInfoLength > 0) + { + parser.PMTProgramDescriptorLengthParse = 2; + } + } + if (parser.PMTProgramInfoLength == 0) + { + parser.PMTTransferState = true; + } + } + } + if (parser.PacketLength == 0) + { + parser.SyncState = false; + } + } + else if (parser.Stream != null && + parser.StreamState != null && + parser.TransportScramblingControl == 0) + { + TSStream stream = parser.Stream; + TSStreamState streamState = parser.StreamState; + + streamState.Parse = + (streamState.Parse << 8) + buffer[i]; + + if (streamState.TransferState) + { + if ((bufferLength - i) >= streamState.PacketLength && + streamState.PacketLength > 0) + { + offset = streamState.PacketLength; + } + else + { + offset = (bufferLength - i); + } + if (parser.PacketLength <= offset) + { + offset = parser.PacketLength; + } + streamState.TransferLength = offset; + + if (!stream.IsInitialized || + stream.IsVideoStream) + { + streamState.StreamBuffer.Add( + buffer, i, offset); + } + else + { + streamState.StreamBuffer.TransferLength += offset; + } + + i += (int)(streamState.TransferLength - 1); + streamState.PacketLength -= streamState.TransferLength; + parser.PacketLength -= (byte)streamState.TransferLength; + + streamState.TotalBytes += (ulong)streamState.TransferLength; + streamState.WindowBytes += (ulong)streamState.TransferLength; + + if (streamState.PacketLength == 0) + { + streamState.TransferState = false; + streamState.TransferCount++; + bool isFinished = ScanStream( + stream, + streamState, + streamState.StreamBuffer); + + if (!isFullScan && isFinished) + { + return; + } + } + } + else + { + --parser.PacketLength; + + bool headerFound = false; + if (stream.IsVideoStream && + streamState.Parse == 0x000001FD) + { + headerFound = true; + } + if (stream.IsVideoStream && + streamState.Parse >= 0x000001E0 && + streamState.Parse <= 0x000001EF) + { + headerFound = true; + } + if (stream.IsAudioStream && + streamState.Parse == 0x000001BD) + { + headerFound = true; + } + if (stream.IsAudioStream && + (streamState.Parse == 0x000001FA || + streamState.Parse == 0x000001FD)) + { + headerFound = true; + } + + if (!stream.IsVideoStream && + !stream.IsAudioStream && + (streamState.Parse == 0x000001FA || + streamState.Parse == 0x000001FD || + streamState.Parse == 0x000001BD || + (streamState.Parse >= 0x000001E0 && + streamState.Parse <= 0x000001EF))) + { + headerFound = true; + } + + if (headerFound) + { + streamState.PacketLengthParse = 2; +#if DEBUG + streamState.PESHeaderIndex = 0; + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)((streamState.Parse >> 24) & 0xFF); + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)((streamState.Parse >> 16) & 0xFF); + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)((streamState.Parse >> 8) & 0xFF); + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xFF); +#endif + } + else if (streamState.PacketLengthParse > 0) + { + --streamState.PacketLengthParse; + switch (streamState.PacketLengthParse) + { + case 1: +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xFF); +#endif + break; + + case 0: + streamState.PacketLength = + (int)(streamState.Parse & 0xFFFF); + streamState.PacketParse = 3; +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xFF); +#endif + break; + } + } + else if (streamState.PacketParse > 0) + { + --streamState.PacketLength; + --streamState.PacketParse; + + switch (streamState.PacketParse) + { + case 2: +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xFF); +#endif + break; + + case 1: + streamState.PESHeaderFlags = + (byte)(streamState.Parse & 0xFF); +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xFF); +#endif + break; + + case 0: + streamState.PESHeaderLength = + (byte)(streamState.Parse & 0xFF); +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xFF); +#endif + if ((streamState.PESHeaderFlags & 0xC0) == 0x80) + { + streamState.PTSParse = 5; + } + else if ((streamState.PESHeaderFlags & 0xC0) == 0xC0) + { + streamState.DTSParse = 10; + } + if (streamState.PESHeaderLength == 0) + { + streamState.TransferState = true; + } + break; + } + } + else if (streamState.PTSParse > 0) + { + --streamState.PacketLength; + --streamState.PESHeaderLength; + --streamState.PTSParse; + + switch (streamState.PTSParse) + { + case 4: + streamState.PTSTemp = + ((streamState.Parse & 0xE) << 29); +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xff); +#endif + break; + + case 3: + streamState.PTSTemp |= + ((streamState.Parse & 0xFF) << 22); +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xFF); +#endif + break; + + case 2: + streamState.PTSTemp |= + ((streamState.Parse & 0xFE) << 14); +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xFF); +#endif + break; + + case 1: + streamState.PTSTemp |= + ((streamState.Parse & 0xFF) << 7); +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xFF); +#endif + break; + + case 0: + streamState.PTSTemp |= + ((streamState.Parse & 0xFE) >> 1); +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xff); +#endif + streamState.PTS = streamState.PTSTemp; + + if (streamState.PTS > streamState.PTSLast) + { + if (streamState.PTSLast > 0) + { + streamState.PTSTransfer = (streamState.PTS - streamState.PTSLast); + } + streamState.PTSLast = streamState.PTS; + } + + streamState.PTSDiff = streamState.PTS - streamState.DTSPrev; + + if (streamState.PTSCount > 0 && + stream.IsVideoStream) + { + UpdateStreamBitrates(stream.PID, streamState.PTS, streamState.PTSDiff); + if (streamState.DTSTemp < parser.PTSFirst) + { + parser.PTSFirst = streamState.DTSTemp; + } + if (streamState.DTSTemp > parser.PTSLast) + { + parser.PTSLast = streamState.DTSTemp; + } + Length = (double)(parser.PTSLast - parser.PTSFirst) / 90000; + } + + streamState.DTSPrev = streamState.PTS; + streamState.PTSCount++; + if (streamState.PESHeaderLength == 0) + { + streamState.TransferState = true; + } + break; + } + } + else if (streamState.DTSParse > 0) + { + --streamState.PacketLength; + --streamState.PESHeaderLength; + --streamState.DTSParse; + + switch (streamState.DTSParse) + { + case 9: + streamState.PTSTemp = + ((streamState.Parse & 0xE) << 29); +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xFF); +#endif + break; + + case 8: + streamState.PTSTemp |= + ((streamState.Parse & 0xFF) << 22); +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xFF); +#endif + break; + + case 7: + streamState.PTSTemp |= + ((streamState.Parse & 0xFE) << 14); +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xff); +#endif + break; + + case 6: + streamState.PTSTemp |= + ((streamState.Parse & 0xFF) << 7); +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xFF); +#endif + break; + + case 5: + streamState.PTSTemp |= + ((streamState.Parse & 0xFE) >> 1); +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xff); +#endif + streamState.PTS = streamState.PTSTemp; + if (streamState.PTS > streamState.PTSLast) + { + streamState.PTSLast = streamState.PTS; + } + break; + + case 4: + streamState.DTSTemp = + ((streamState.Parse & 0xE) << 29); +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xff); +#endif + break; + + case 3: + streamState.DTSTemp |= + ((streamState.Parse & 0xFF) << 22); +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xff); +#endif + break; + + case 2: + streamState.DTSTemp |= + ((streamState.Parse & 0xFE) << 14); +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xff); +#endif + break; + + case 1: + streamState.DTSTemp |= + ((streamState.Parse & 0xFF) << 7); +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xFF); +#endif + break; + + case 0: + streamState.DTSTemp |= + ((streamState.Parse & 0xFE) >> 1); +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xff); +#endif + streamState.PTSDiff = streamState.DTSTemp - streamState.DTSPrev; + + if (streamState.PTSCount > 0 && + stream.IsVideoStream) + { + UpdateStreamBitrates(stream.PID, streamState.DTSTemp, streamState.PTSDiff); + if (streamState.DTSTemp < parser.PTSFirst) + { + parser.PTSFirst = streamState.DTSTemp; + } + if (streamState.DTSTemp > parser.PTSLast) + { + parser.PTSLast = streamState.DTSTemp; + } + Length = (double)(parser.PTSLast - parser.PTSFirst) / 90000; + } + streamState.DTSPrev = streamState.DTSTemp; + streamState.PTSCount++; + if (streamState.PESHeaderLength == 0) + { + streamState.TransferState = true; + } + break; + } + } + else if (streamState.PESHeaderLength > 0) + { + --streamState.PacketLength; + --streamState.PESHeaderLength; +#if DEBUG + streamState.PESHeader[streamState.PESHeaderIndex++] = + (byte)(streamState.Parse & 0xFF); +#endif + if (streamState.PESHeaderLength == 0) + { + streamState.TransferState = true; + } + } + } + if (parser.PacketLength == 0) + { + parser.SyncState = false; + } + } + else + { + parser.PacketLength--; + if ((bufferLength - i) >= parser.PacketLength) + { + i = i + parser.PacketLength; + parser.PacketLength = 0; + } + else + { + parser.PacketLength -= (byte)((bufferLength - i) + 1); + i = bufferLength; + } + if (parser.PacketLength == 0) + { + parser.SyncState = false; + } + } + } + Size += bufferLength; + } + + ulong PTSLast = 0; + ulong PTSDiff = 0; + foreach (TSStream stream in Streams.Values) + { + if (!stream.IsVideoStream) continue; + + if (StreamStates.ContainsKey(stream.PID) && + StreamStates[stream.PID].PTSLast > PTSLast) + { + PTSLast = StreamStates[stream.PID].PTSLast; + PTSDiff = PTSLast - StreamStates[stream.PID].DTSPrev; + } + UpdateStreamBitrates(stream.PID, PTSLast, PTSDiff); + } + } + finally + { + if (fileStream != null) + { + fileStream.Close(); + } + } + } + + private TSStream CreateStream( + ushort streamPID, + byte streamType, + List streamDescriptors) + { + TSStream stream = null; + + switch ((TSStreamType)streamType) + { + case TSStreamType.MVC_VIDEO: + case TSStreamType.AVC_VIDEO: + case TSStreamType.MPEG1_VIDEO: + case TSStreamType.MPEG2_VIDEO: + case TSStreamType.VC1_VIDEO: + { + stream = new TSVideoStream(); + } + break; + + case TSStreamType.AC3_AUDIO: + case TSStreamType.AC3_PLUS_AUDIO: + case TSStreamType.AC3_PLUS_SECONDARY_AUDIO: + case TSStreamType.AC3_TRUE_HD_AUDIO: + case TSStreamType.DTS_AUDIO: + case TSStreamType.DTS_HD_AUDIO: + case TSStreamType.DTS_HD_MASTER_AUDIO: + case TSStreamType.DTS_HD_SECONDARY_AUDIO: + case TSStreamType.LPCM_AUDIO: + case TSStreamType.MPEG1_AUDIO: + case TSStreamType.MPEG2_AUDIO: + { + stream = new TSAudioStream(); + } + break; + + case TSStreamType.INTERACTIVE_GRAPHICS: + case TSStreamType.PRESENTATION_GRAPHICS: + { + stream = new TSGraphicsStream(); + } + break; + + case TSStreamType.SUBTITLE: + { + stream = new TSTextStream(); + } + break; + + default: + break; + } + + if (stream != null && + !Streams.ContainsKey(streamPID)) + { + stream.PID = streamPID; + stream.StreamType = (TSStreamType)streamType; + stream.Descriptors = streamDescriptors; + Streams[stream.PID] = stream; + } + if (!StreamDiagnostics.ContainsKey(streamPID)) + { + StreamDiagnostics[streamPID] = + new List(); + } + + return stream; + } + } +} diff --git a/Bootstrapper/readme.txt b/Bootstrapper/readme.txt new file mode 100644 index 0000000000..0fdcf2aa57 --- /dev/null +++ b/Bootstrapper/readme.txt @@ -0,0 +1,3 @@ +If Publishing, the folders in here must be copied to: + +C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\Bootstrapper\Packages \ No newline at end of file diff --git a/Bootstrapper/vcredist10_x86/en/package.xml b/Bootstrapper/vcredist10_x86/en/package.xml new file mode 100644 index 0000000000..613fc0b84c --- /dev/null +++ b/Bootstrapper/vcredist10_x86/en/package.xml @@ -0,0 +1,20 @@ + + + + + + + Visual C++ 2010 Runtime Libraries (x86) + en + You do not have the permissions required to install Visual C++ 2010 Runtime Libraries (x86). Please contact your administrator. + Installation of Visual C++ 2010 Runtime Libraries (x86) is not supported on Windows 95. Contact your application vendor. + Installation of Visual C++ 2010 Runtime Libraries (x86) is not supported on Windows NT 4.0. Contact your application vendor. + A failure occurred attempting to install Visual C++ 2010 Runtime Libraries (x86). + http://go.microsoft.com/fwlink/?LinkID=210621 + + + diff --git a/Bootstrapper/vcredist10_x86/product.xml b/Bootstrapper/vcredist10_x86/product.xml new file mode 100644 index 0000000000..76edde6187 --- /dev/null +++ b/Bootstrapper/vcredist10_x86/product.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MediaBrowser.Api/ApiService.cs b/MediaBrowser.Api/ApiService.cs index 0fef1cb574..68ba6d5225 100644 --- a/MediaBrowser.Api/ApiService.cs +++ b/MediaBrowser.Api/ApiService.cs @@ -1,438 +1,78 @@ -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Model.DTO; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api -{ - /// - /// Contains some helpers for the api - /// - public static class ApiService - { - /// - /// Gets an Item by Id, or the root item if none is supplied - /// - public static BaseItem GetItemById(string id) - { - Guid guid = string.IsNullOrEmpty(id) ? Guid.Empty : new Guid(id); - - return Kernel.Instance.GetItemById(guid); - } - - /// - /// Gets a User by Id - /// - /// Whether or not to update the user's LastActivityDate - public static User GetUserById(string id, bool logActivity) - { - var guid = new Guid(id); - - var user = Kernel.Instance.Users.FirstOrDefault(u => u.Id == guid); - - if (logActivity) - { - LogUserActivity(user); - } - - return user; - } - - /// - /// Gets the default User - /// - /// Whether or not to update the user's LastActivityDate - public static User GetDefaultUser(bool logActivity) - { - User user = Kernel.Instance.GetDefaultUser(); - - if (logActivity) - { - LogUserActivity(user); - } - - return user; - } - - /// - /// Updates LastActivityDate for a given User - /// - public static void LogUserActivity(User user) - { - user.LastActivityDate = DateTime.UtcNow; - Kernel.Instance.SaveUser(user); - } - - /// - /// Converts a BaseItem to a DTOBaseItem - /// - public async static Task GetDtoBaseItem(BaseItem item, User user, - bool includeChildren = true, - bool includePeople = true) - { - var dto = new DtoBaseItem(); - - var tasks = new List(); - - tasks.Add(AttachStudios(dto, item)); - - if (includeChildren) - { - tasks.Add(AttachChildren(dto, item, user)); - tasks.Add(AttachLocalTrailers(dto, item, user)); - } - - if (includePeople) - { - tasks.Add(AttachPeople(dto, item)); - } - - AttachBasicFields(dto, item, user); - - // Make sure all the tasks we kicked off have completed. - if (tasks.Count > 0) - { - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - return dto; - } - - /// - /// Sets simple property values on a DTOBaseItem - /// - private static void AttachBasicFields(DtoBaseItem dto, BaseItem item, User user) - { - dto.AspectRatio = item.AspectRatio; - dto.BackdropCount = item.BackdropImagePaths == null ? 0 : item.BackdropImagePaths.Count(); - dto.DateCreated = item.DateCreated; - dto.DisplayMediaType = item.DisplayMediaType; - - if (item.Genres != null) - { - dto.Genres = item.Genres.ToArray(); - } - - dto.HasArt = !string.IsNullOrEmpty(item.ArtImagePath); - dto.HasBanner = !string.IsNullOrEmpty(item.BannerImagePath); - dto.HasLogo = !string.IsNullOrEmpty(item.LogoImagePath); - dto.HasPrimaryImage = !string.IsNullOrEmpty(item.PrimaryImagePath); - dto.HasThumb = !string.IsNullOrEmpty(item.ThumbnailImagePath); - dto.Id = item.Id; - dto.IsNew = item.IsRecentlyAdded(user); - dto.IndexNumber = item.IndexNumber; - dto.IsFolder = item.IsFolder; - dto.Language = item.Language; - dto.LocalTrailerCount = item.LocalTrailers == null ? 0 : item.LocalTrailers.Count(); - dto.Name = item.Name; - dto.OfficialRating = item.OfficialRating; - dto.Overview = item.Overview; - - // If there are no backdrops, indicate what parent has them in case the Ui wants to allow inheritance - if (dto.BackdropCount == 0) - { - int backdropCount; - dto.ParentBackdropItemId = GetParentBackdropItemId(item, out backdropCount); - dto.ParentBackdropCount = backdropCount; - } - - if (item.Parent != null) - { - dto.ParentId = item.Parent.Id; - } - - dto.ParentIndexNumber = item.ParentIndexNumber; - - // If there is no logo, indicate what parent has one in case the Ui wants to allow inheritance - if (!dto.HasLogo) - { - dto.ParentLogoItemId = GetParentLogoItemId(item); - } - - dto.Path = item.Path; - - dto.PremiereDate = item.PremiereDate; - dto.ProductionYear = item.ProductionYear; - dto.ProviderIds = item.ProviderIds; - dto.RunTimeTicks = item.RunTimeTicks; - dto.SortName = item.SortName; - - if (item.Taglines != null) - { - dto.Taglines = item.Taglines.ToArray(); - } - - dto.TrailerUrl = item.TrailerUrl; - dto.Type = item.GetType().Name; - dto.CommunityRating = item.CommunityRating; - - dto.UserData = GetDtoUserItemData(item.GetUserData(user, false)); - - var folder = item as Folder; - - if (folder != null) - { - dto.SpecialCounts = folder.GetSpecialCounts(user); - - dto.IsRoot = folder.IsRoot; - dto.IsVirtualFolder = folder.IsVirtualFolder; - } - - // Add AudioInfo - var audio = item as Audio; - - if (audio != null) - { - dto.AudioInfo = new AudioInfo - { - Album = audio.Album, - AlbumArtist = audio.AlbumArtist, - Artist = audio.Artist, - BitRate = audio.BitRate, - Channels = audio.Channels - }; - } - - // Add VideoInfo - var video = item as Video; - - if (video != null) - { - dto.VideoInfo = new VideoInfo - { - Height = video.Height, - Width = video.Width, - Codec = video.Codec, - VideoType = video.VideoType, - ScanType = video.ScanType - }; - - if (video.AudioStreams != null) - { - dto.VideoInfo.AudioStreams = video.AudioStreams.ToArray(); - } - - if (video.Subtitles != null) - { - dto.VideoInfo.Subtitles = video.Subtitles.ToArray(); - } - } - - // Add SeriesInfo - var series = item as Series; - - if (series != null) - { - DayOfWeek[] airDays = series.AirDays == null ? new DayOfWeek[] { } : series.AirDays.ToArray(); - - dto.SeriesInfo = new SeriesInfo - { - AirDays = airDays, - AirTime = series.AirTime, - Status = series.Status - }; - } - - // Add MovieInfo - var movie = item as Movie; - - if (movie != null) - { - int specialFeatureCount = movie.SpecialFeatures == null ? 0 : movie.SpecialFeatures.Count(); - - dto.MovieInfo = new MovieInfo - { - SpecialFeatureCount = specialFeatureCount - }; - } - } - - /// - /// Attaches Studio DTO's to a DTOBaseItem - /// - private static async Task AttachStudios(DtoBaseItem dto, BaseItem item) - { - // Attach Studios by transforming them into BaseItemStudio (DTO) - if (item.Studios != null) - { - Studio[] entities = await Task.WhenAll(item.Studios.Select(c => Kernel.Instance.ItemController.GetStudio(c))).ConfigureAwait(false); - - dto.Studios = new BaseItemStudio[entities.Length]; - - for (int i = 0; i < entities.Length; i++) - { - Studio entity = entities[i]; - var baseItemStudio = new BaseItemStudio{}; - - baseItemStudio.Name = entity.Name; - - baseItemStudio.HasImage = !string.IsNullOrEmpty(entity.PrimaryImagePath); - - dto.Studios[i] = baseItemStudio; - } - } - } - - /// - /// Attaches child DTO's to a DTOBaseItem - /// - private static async Task AttachChildren(DtoBaseItem dto, BaseItem item, User user) - { - var folder = item as Folder; - - if (folder != null) - { - IEnumerable children = folder.GetChildren(user); - - dto.Children = await Task.WhenAll(children.Select(c => GetDtoBaseItem(c, user, false, false))).ConfigureAwait(false); - } - } - - /// - /// Attaches trailer DTO's to a DTOBaseItem - /// - private static async Task AttachLocalTrailers(DtoBaseItem dto, BaseItem item, User user) - { - if (item.LocalTrailers != null && item.LocalTrailers.Any()) - { - dto.LocalTrailers = await Task.WhenAll(item.LocalTrailers.Select(c => GetDtoBaseItem(c, user, false, false))).ConfigureAwait(false); - } - } - - /// - /// Attaches People DTO's to a DTOBaseItem - /// - private static async Task AttachPeople(DtoBaseItem dto, BaseItem item) - { - // Attach People by transforming them into BaseItemPerson (DTO) - if (item.People != null) - { - IEnumerable entities = await Task.WhenAll(item.People.Select(c => Kernel.Instance.ItemController.GetPerson(c.Key))).ConfigureAwait(false); - - dto.People = item.People.Select(p => - { - var baseItemPerson = new BaseItemPerson{}; - - baseItemPerson.Name = p.Key; - baseItemPerson.Overview = p.Value.Overview; - baseItemPerson.Type = p.Value.Type; - - Person ibnObject = entities.First(i => i.Name.Equals(p.Key, StringComparison.OrdinalIgnoreCase)); - - if (ibnObject != null) - { - baseItemPerson.HasImage = !string.IsNullOrEmpty(ibnObject.PrimaryImagePath); - } - - return baseItemPerson; - }).ToArray(); - } - } - - /// - /// If an item does not any backdrops, this can be used to find the first parent that does have one - /// - private static Guid? GetParentBackdropItemId(BaseItem item, out int backdropCount) - { - backdropCount = 0; - - var parent = item.Parent; - - while (parent != null) - { - if (parent.BackdropImagePaths != null && parent.BackdropImagePaths.Any()) - { - backdropCount = parent.BackdropImagePaths.Count(); - return parent.Id; - } - - parent = parent.Parent; - } - - return null; - } - - /// - /// If an item does not have a logo, this can be used to find the first parent that does have one - /// - private static Guid? GetParentLogoItemId(BaseItem item) - { - var parent = item.Parent; - - while (parent != null) - { - if (!string.IsNullOrEmpty(parent.LogoImagePath)) - { - return parent.Id; - } - - parent = parent.Parent; - } - - return null; - } - - /// - /// Gets an ImagesByName entity along with the number of items containing it - /// - public static IbnItem GetIbnItem(BaseEntity entity, int itemCount) - { - return new IbnItem - { - Id = entity.Id, - BaseItemCount = itemCount, - HasImage = !string.IsNullOrEmpty(entity.PrimaryImagePath), - Name = entity.Name - }; - } - - /// - /// Converts a User to a DTOUser - /// - public static DtoUser GetDtoUser(User user) - { - return new DtoUser - { - Id = user.Id, - Name = user.Name, - HasImage = !string.IsNullOrEmpty(user.PrimaryImagePath), - HasPassword = !string.IsNullOrEmpty(user.Password), - LastActivityDate = user.LastActivityDate, - LastLoginDate = user.LastLoginDate - }; - } - - /// - /// Converts a UserItemData to a DTOUserItemData - /// - public static DtoUserItemData GetDtoUserItemData(UserItemData data) - { - if (data == null) - { - return null; - } - - return new DtoUserItemData - { - IsFavorite = data.IsFavorite, - Likes = data.Likes, - PlaybackPositionTicks = data.PlaybackPositionTicks, - PlayCount = data.PlayCount, - Rating = data.Rating - }; - } - - public static bool IsApiUrlMatch(string url, HttpListenerRequest request) - { - url = "/api/" + url; - - return request.Url.LocalPath.EndsWith(url, StringComparison.OrdinalIgnoreCase); - } - } -} +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Connectivity; +using ServiceStack.Common.Web; +using System; +using System.Net; +using System.Threading.Tasks; + +namespace MediaBrowser.Api +{ + /// + /// Contains some helpers for the api + /// + public static class ApiService + { + /// + /// Gets a User by Id + /// + /// The id of the user + /// User. + /// id + public static User GetUserById(string id) + { + if (string.IsNullOrEmpty(id)) + { + throw new ArgumentNullException("id"); + } + + var guid = new Guid(id); + + return Kernel.Instance.GetUserById(guid); + } + + /// + /// Determines whether [is API URL match] [the specified URL]. + /// + /// The URL. + /// The request. + /// true if [is API URL match] [the specified URL]; otherwise, false. + public static bool IsApiUrlMatch(string url, HttpListenerRequest request) + { + url = "/api/" + url; + + return request.Url.LocalPath.EndsWith(url, StringComparison.OrdinalIgnoreCase); + } + + ///// + ///// Gets the current user. + ///// + ///// The request. + ///// Task{User}. + //public static async Task GetCurrentUser(AuthenticatedRequest request) + //{ + // var user = GetUserById(request.UserId); + + // if (user == null) + // { + // throw HttpError.Unauthorized("Invalid user or password entered."); + // } + + // var clientType = ClientType.Other; + + // if (!string.IsNullOrEmpty(request.Client)) + // { + // ClientType type; + + // if (Enum.TryParse(request.Client, true, out type)) + // { + // clientType = type; + // } + // } + + // await Kernel.Instance.UserManager.LogUserActivity(user, clientType, request.Device).ConfigureAwait(false); + + // return user; + //} + } +} diff --git a/MediaBrowser.Api/Drawing/DrawingUtils.cs b/MediaBrowser.Api/Drawing/DrawingUtils.cs deleted file mode 100644 index f76a74218f..0000000000 --- a/MediaBrowser.Api/Drawing/DrawingUtils.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Drawing; - -namespace MediaBrowser.Api.Drawing -{ - public static class DrawingUtils - { - /// - /// Resizes a set of dimensions - /// - public static Size Resize(int currentWidth, int currentHeight, int? width, int? height, int? maxWidth, int? maxHeight) - { - return Resize(new Size(currentWidth, currentHeight), width, height, maxWidth, maxHeight); - } - - /// - /// Resizes a set of dimensions - /// - /// The original size object - /// A new fixed width, if desired - /// A new fixed neight, if desired - /// A max fixed width, if desired - /// A max fixed height, if desired - /// A new size object - public static Size Resize(Size size, int? width, int? height, int? maxWidth, int? maxHeight) - { - decimal newWidth = size.Width; - decimal newHeight = size.Height; - - if (width.HasValue && height.HasValue) - { - newWidth = width.Value; - newHeight = height.Value; - } - - else if (height.HasValue) - { - newWidth = GetNewWidth(newHeight, newWidth, height.Value); - newHeight = height.Value; - } - - else if (width.HasValue) - { - newHeight = GetNewHeight(newHeight, newWidth, width.Value); - newWidth = width.Value; - } - - if (maxHeight.HasValue && maxHeight < newHeight) - { - newWidth = GetNewWidth(newHeight, newWidth, maxHeight.Value); - newHeight = maxHeight.Value; - } - - if (maxWidth.HasValue && maxWidth < newWidth) - { - newHeight = GetNewHeight(newHeight, newWidth, maxWidth.Value); - newWidth = maxWidth.Value; - } - - return new Size(Convert.ToInt32(newWidth), Convert.ToInt32(newHeight)); - } - - private static decimal GetNewWidth(decimal currentHeight, decimal currentWidth, int newHeight) - { - decimal scaleFactor = newHeight; - scaleFactor /= currentHeight; - scaleFactor *= currentWidth; - - return scaleFactor; - } - - private static decimal GetNewHeight(decimal currentHeight, decimal currentWidth, int newWidth) - { - decimal scaleFactor = newWidth; - scaleFactor /= currentWidth; - scaleFactor *= currentHeight; - - return scaleFactor; - } - } -} diff --git a/MediaBrowser.Api/Drawing/ImageProcessor.cs b/MediaBrowser.Api/Drawing/ImageProcessor.cs deleted file mode 100644 index 1a471acf54..0000000000 --- a/MediaBrowser.Api/Drawing/ImageProcessor.cs +++ /dev/null @@ -1,148 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Entities; -using System; -using System.Drawing; -using System.Drawing.Drawing2D; -using System.Drawing.Imaging; -using System.IO; -using System.Linq; - -namespace MediaBrowser.Api.Drawing -{ - public static class ImageProcessor - { - /// - /// Processes an image by resizing to target dimensions - /// - /// The entity that owns the image - /// The image type - /// The image index (currently only used with backdrops) - /// The stream to save the new image to - /// Use if a fixed width is required. Aspect ratio will be preserved. - /// Use if a fixed height is required. Aspect ratio will be preserved. - /// Use if a max width is required. Aspect ratio will be preserved. - /// Use if a max height is required. Aspect ratio will be preserved. - /// Quality level, from 0-100. Currently only applies to JPG. The default value should suffice. - public static void ProcessImage(BaseEntity entity, ImageType imageType, int imageIndex, Stream toStream, int? width, int? height, int? maxWidth, int? maxHeight, int? quality) - { - Image originalImage = Image.FromFile(GetImagePath(entity, imageType, imageIndex)); - - // Determine the output size based on incoming parameters - Size newSize = DrawingUtils.Resize(originalImage.Size, width, height, maxWidth, maxHeight); - - Bitmap thumbnail; - - // Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here - if (originalImage.PixelFormat.HasFlag(PixelFormat.Indexed)) - { - thumbnail = new Bitmap(originalImage, newSize.Width, newSize.Height); - } - else - { - thumbnail = new Bitmap(newSize.Width, newSize.Height, originalImage.PixelFormat); - } - - thumbnail.MakeTransparent(); - - // Preserve the original resolution - thumbnail.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution); - - Graphics thumbnailGraph = Graphics.FromImage(thumbnail); - - thumbnailGraph.CompositingQuality = CompositingQuality.HighQuality; - thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality; - thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic; - thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality; - thumbnailGraph.CompositingMode = CompositingMode.SourceOver; - - thumbnailGraph.DrawImage(originalImage, 0, 0, newSize.Width, newSize.Height); - - ImageFormat outputFormat = originalImage.RawFormat; - - // Write to the output stream - SaveImage(outputFormat, thumbnail, toStream, quality); - - thumbnailGraph.Dispose(); - thumbnail.Dispose(); - originalImage.Dispose(); - } - - public static string GetImagePath(BaseEntity entity, ImageType imageType, int imageIndex) - { - var item = entity as BaseItem; - - if (item != null) - { - if (imageType == ImageType.Logo) - { - return item.LogoImagePath; - } - if (imageType == ImageType.Backdrop) - { - return item.BackdropImagePaths.ElementAt(imageIndex); - } - if (imageType == ImageType.Banner) - { - return item.BannerImagePath; - } - if (imageType == ImageType.Art) - { - return item.ArtImagePath; - } - if (imageType == ImageType.Thumbnail) - { - return item.ThumbnailImagePath; - } - } - - return entity.PrimaryImagePath; - } - - public static void SaveImage(ImageFormat outputFormat, Image newImage, Stream toStream, int? quality) - { - // Use special save methods for jpeg and png that will result in a much higher quality image - // All other formats use the generic Image.Save - if (ImageFormat.Jpeg.Equals(outputFormat)) - { - SaveJpeg(newImage, toStream, quality); - } - else if (ImageFormat.Png.Equals(outputFormat)) - { - newImage.Save(toStream, ImageFormat.Png); - } - else - { - newImage.Save(toStream, outputFormat); - } - } - - public static void SaveJpeg(Image image, Stream target, int? quality) - { - if (!quality.HasValue) - { - quality = 90; - } - - using (var encoderParameters = new EncoderParameters(1)) - { - encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, quality.Value); - image.Save(target, GetImageCodecInfo("image/jpeg"), encoderParameters); - } - } - - public static ImageCodecInfo GetImageCodecInfo(string mimeType) - { - ImageCodecInfo[] info = ImageCodecInfo.GetImageEncoders(); - - for (int i = 0; i < info.Length; i++) - { - ImageCodecInfo ici = info[i]; - if (ici.MimeType.Equals(mimeType, StringComparison.OrdinalIgnoreCase)) - { - return ici; - } - } - return info[1]; - } - } -} diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs new file mode 100644 index 0000000000..e552c7f1de --- /dev/null +++ b/MediaBrowser.Api/EnvironmentService.cs @@ -0,0 +1,200 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.IO; +using ServiceStack.ServiceHost; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Globalization; +using System.IO; +using System.Linq; + +namespace MediaBrowser.Api +{ + /// + /// Class GetDirectoryContents + /// + [Route("/Environment/DirectoryContents", "GET")] + public class GetDirectoryContents : IReturn> + { + /// + /// Gets or sets the path. + /// + /// The path. + public string Path { get; set; } + /// + /// Gets or sets a value indicating whether [include files]. + /// + /// true if [include files]; otherwise, false. + public bool IncludeFiles { get; set; } + /// + /// Gets or sets a value indicating whether [include directories]. + /// + /// true if [include directories]; otherwise, false. + public bool IncludeDirectories { get; set; } + /// + /// Gets or sets a value indicating whether [include hidden]. + /// + /// true if [include hidden]; otherwise, false. + public bool IncludeHidden { get; set; } + } + + /// + /// Class GetDrives + /// + [Route("/Environment/Drives", "GET")] + public class GetDrives : IReturn> + { + } + + /// + /// Class GetNetworkComputers + /// + [Route("/Environment/NetworkComputers", "GET")] + public class GetNetworkComputers : IReturn> + { + } + + /// + /// Class EnvironmentService + /// + [Export(typeof(IRestfulService))] + public class EnvironmentService : BaseRestService + { + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + /// Path + /// + public object Get(GetDirectoryContents request) + { + var path = request.Path; + + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("Path"); + } + + // Reject invalid input + if (!Path.IsPathRooted(path)) + { + throw new ArgumentException(string.Format("Invalid path: {0}", path)); + } + + if (path.StartsWith(NetworkPrefix, StringComparison.OrdinalIgnoreCase) && path.LastIndexOf('\\') == 1) + { + return GetNetworkShares(path).ToList(); + } + + return GetFileSystemEntries(request).ToList(); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetDrives request) + { + return GetDrives().ToList(); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetNetworkComputers request) + { + return GetNetworkComputers().ToList(); + } + + /// + /// Gets the list that is returned when an empty path is supplied + /// + /// IEnumerable{FileSystemEntryInfo}. + private IEnumerable GetDrives() + { + // Only include drives in the ready state or this method could end up being very slow, waiting for drives to timeout + return DriveInfo.GetDrives().Where(d => d.IsReady).Select(d => new FileSystemEntryInfo + { + Name = GetName(d), + Path = d.RootDirectory.FullName, + Type = FileSystemEntryType.Directory + + }); + } + + /// + /// Gets the network computers. + /// + /// IEnumerable{FileSystemEntryInfo}. + private IEnumerable GetNetworkComputers() + { + return NetUtils.GetNetworkComputers().Select(c => new FileSystemEntryInfo + { + Name = c, + Path = NetworkPrefix + c, + Type = FileSystemEntryType.NetworkComputer + }); + } + + /// + /// Gets the name. + /// + /// The drive. + /// System.String. + private string GetName(DriveInfo drive) + { + return drive.Name; + } + + /// + /// Gets the network shares. + /// + /// The path. + /// IEnumerable{FileSystemEntryInfo}. + private IEnumerable GetNetworkShares(string path) + { + return new ShareCollection(path).OfType().Where(s => s.ShareType == ShareType.Disk).Select(c => new FileSystemEntryInfo + { + Name = c.NetName, + Path = Path.Combine(path, c.NetName), + Type = FileSystemEntryType.NetworkShare + }); + } + + /// + /// Gets the file system entries. + /// + /// The request. + /// IEnumerable{FileSystemEntryInfo}. + private IEnumerable GetFileSystemEntries(GetDirectoryContents request) + { + var fileSystemEntries = FileSystem.GetFileSystemEntries(request.Path, "*", request.IncludeFiles, request.IncludeDirectories).Where(f => !f.IsSystemFile); + + if (!request.IncludeHidden) + { + fileSystemEntries = fileSystemEntries.Where(f => !f.IsHidden); + } + + return fileSystemEntries.Select(f => new FileSystemEntryInfo + { + Name = f.cFileName, + Path = f.Path, + Type = f.IsDirectory ? FileSystemEntryType.Directory : FileSystemEntryType.File + }); + } + + /// + /// Gets the network prefix. + /// + /// The network prefix. + private string NetworkPrefix + { + get { return Path.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture) + Path.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture); } + } + } +} diff --git a/MediaBrowser.Api/HttpHandlers/AudioHandler.cs b/MediaBrowser.Api/HttpHandlers/AudioHandler.cs deleted file mode 100644 index 9c16acd2ef..0000000000 --- a/MediaBrowser.Api/HttpHandlers/AudioHandler.cs +++ /dev/null @@ -1,119 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.DTO; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.IO; -using System.Net; - -namespace MediaBrowser.Api.HttpHandlers -{ - /// - /// Supported output formats are: mp3,flac,ogg,wav,asf,wma,aac - /// - [Export(typeof(BaseHandler))] - public class AudioHandler : BaseMediaHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("audio", request); - } - - /// - /// We can output these formats directly, but we cannot encode to them. - /// - protected override IEnumerable UnsupportedOutputEncodingFormats - { - get - { - return new AudioOutputFormats[] { AudioOutputFormats.Aac, AudioOutputFormats.Flac, AudioOutputFormats.Wma }; - } - } - - private int? GetMaxAcceptedBitRate(AudioOutputFormats audioFormat) - { - return GetMaxAcceptedBitRate(audioFormat.ToString()); - } - - private int? GetMaxAcceptedBitRate(string audioFormat) - { - if (audioFormat.Equals("mp3", System.StringComparison.OrdinalIgnoreCase)) - { - return 320000; - } - - return null; - } - - /// - /// Determines whether or not the original file requires transcoding - /// - protected override bool RequiresConversion() - { - if (base.RequiresConversion()) - { - return true; - } - - string currentFormat = Path.GetExtension(LibraryItem.Path).Replace(".", string.Empty); - - int? bitrate = GetMaxAcceptedBitRate(currentFormat); - - // If the bitrate is greater than our desired bitrate, we need to transcode - if (bitrate.HasValue && bitrate.Value < LibraryItem.BitRate) - { - return true; - } - - // If the number of channels is greater than our desired channels, we need to transcode - if (AudioChannels.HasValue && AudioChannels.Value < LibraryItem.Channels) - { - return true; - } - - // If the sample rate is greater than our desired sample rate, we need to transcode - if (AudioSampleRate.HasValue && AudioSampleRate.Value < LibraryItem.SampleRate) - { - return true; - } - - // Yay - return false; - } - - /// - /// Creates arguments to pass to ffmpeg - /// - protected override string GetCommandLineArguments() - { - var audioTranscodeParams = new List(); - - AudioOutputFormats outputFormat = GetConversionOutputFormat(); - - int? bitrate = GetMaxAcceptedBitRate(outputFormat); - - if (bitrate.HasValue) - { - audioTranscodeParams.Add("-ab " + bitrate.Value); - } - - int? channels = GetNumAudioChannelsParam(LibraryItem.Channels); - - if (channels.HasValue) - { - audioTranscodeParams.Add("-ac " + channels.Value); - } - - int? sampleRate = GetSampleRateParam(LibraryItem.SampleRate); - - if (sampleRate.HasValue) - { - audioTranscodeParams.Add("-ar " + sampleRate.Value); - } - - audioTranscodeParams.Add("-f " + outputFormat); - - return "-i \"" + LibraryItem.Path + "\" -vn " + string.Join(" ", audioTranscodeParams.ToArray()) + " -"; - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/BaseMediaHandler.cs b/MediaBrowser.Api/HttpHandlers/BaseMediaHandler.cs deleted file mode 100644 index 96ef606813..0000000000 --- a/MediaBrowser.Api/HttpHandlers/BaseMediaHandler.cs +++ /dev/null @@ -1,255 +0,0 @@ -using MediaBrowser.Common.Logging; -using MediaBrowser.Common.Net; -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - public abstract class BaseMediaHandler : BaseHandler - where TBaseItemType : BaseItem, new() - { - /// - /// Supported values: mp3,flac,ogg,wav,asf,wma,aac - /// - protected virtual IEnumerable OutputFormats - { - get - { - return QueryString["outputformats"].Split(',').Select(o => (TOutputType)Enum.Parse(typeof(TOutputType), o, true)); - } - } - - /// - /// These formats can be outputted directly but cannot be encoded to - /// - protected virtual IEnumerable UnsupportedOutputEncodingFormats - { - get - { - return new TOutputType[] { }; - } - } - - private TBaseItemType _libraryItem; - /// - /// Gets the library item that will be played, if any - /// - protected TBaseItemType LibraryItem - { - get - { - if (_libraryItem == null) - { - string id = QueryString["id"]; - - if (!string.IsNullOrEmpty(id)) - { - _libraryItem = Kernel.Instance.GetItemById(Guid.Parse(id)) as TBaseItemType; - } - } - - return _libraryItem; - } - } - - public int? AudioChannels - { - get - { - string val = QueryString["audiochannels"]; - - if (string.IsNullOrEmpty(val)) - { - return null; - } - - return int.Parse(val); - } - } - - public int? AudioSampleRate - { - get - { - string val = QueryString["audiosamplerate"]; - - if (string.IsNullOrEmpty(val)) - { - return 44100; - } - - return int.Parse(val); - } - } - - protected override Task GetResponseInfo() - { - ResponseInfo info = new ResponseInfo - { - ContentType = MimeTypes.GetMimeType("." + GetConversionOutputFormat()), - CompressResponse = false - }; - - return Task.FromResult(info); - } - - public override Task ProcessRequest(HttpListenerContext ctx) - { - HttpListenerContext = ctx; - - if (!RequiresConversion()) - { - return new StaticFileHandler { Path = LibraryItem.Path }.ProcessRequest(ctx); - } - - return base.ProcessRequest(ctx); - } - - protected abstract string GetCommandLineArguments(); - - /// - /// Gets the format we'll be converting to - /// - protected virtual TOutputType GetConversionOutputFormat() - { - return OutputFormats.First(f => !UnsupportedOutputEncodingFormats.Any(s => s.ToString().Equals(f.ToString(), StringComparison.OrdinalIgnoreCase))); - } - - protected virtual bool RequiresConversion() - { - string currentFormat = Path.GetExtension(LibraryItem.Path).Replace(".", string.Empty); - - if (OutputFormats.Any(f => currentFormat.EndsWith(f.ToString(), StringComparison.OrdinalIgnoreCase))) - { - // We can output these files directly, but we can't encode them - if (UnsupportedOutputEncodingFormats.Any(f => currentFormat.EndsWith(f.ToString(), StringComparison.OrdinalIgnoreCase))) - { - return false; - } - } - else - { - // If it's not in a format the consumer accepts, return true - return true; - } - - return false; - } - - private FileStream LogFileStream { get; set; } - - protected async override Task WriteResponseToOutputStream(Stream stream) - { - var startInfo = new ProcessStartInfo{}; - - startInfo.CreateNoWindow = true; - - startInfo.UseShellExecute = false; - - // Must consume both or ffmpeg may hang due to deadlocks. See comments below. - startInfo.RedirectStandardOutput = true; - startInfo.RedirectStandardError = true; - - startInfo.FileName = Kernel.Instance.ApplicationPaths.FFMpegPath; - startInfo.WorkingDirectory = Kernel.Instance.ApplicationPaths.FFMpegDirectory; - startInfo.Arguments = GetCommandLineArguments(); - - Logger.LogInfo(startInfo.FileName + " " + startInfo.Arguments); - - var process = new Process{}; - process.StartInfo = startInfo; - - // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - LogFileStream = new FileStream(Path.Combine(Kernel.Instance.ApplicationPaths.LogDirectoryPath, "ffmpeg-" + Guid.NewGuid().ToString() + ".txt"), FileMode.Create); - - process.EnableRaisingEvents = true; - - process.Exited += ProcessExited; - - try - { - process.Start(); - - // MUST read both stdout and stderr asynchronously or a deadlock may occurr - - // Kick off two tasks - Task mediaTask = process.StandardOutput.BaseStream.CopyToAsync(stream); - Task debugLogTask = process.StandardError.BaseStream.CopyToAsync(LogFileStream); - - await mediaTask.ConfigureAwait(false); - //await debugLogTask.ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.LogException(ex); - - // Hate having to do this - try - { - process.Kill(); - } - catch - { - } - } - } - - void ProcessExited(object sender, EventArgs e) - { - if (LogFileStream != null) - { - LogFileStream.Dispose(); - } - - var process = sender as Process; - - Logger.LogInfo("FFMpeg exited with code " + process.ExitCode); - - process.Dispose(); - } - - /// - /// Gets the number of audio channels to specify on the command line - /// - protected int? GetNumAudioChannelsParam(int libraryItemChannels) - { - // If the user requested a max number of channels - if (AudioChannels.HasValue) - { - // Only specify the param if we're going to downmix - if (AudioChannels.Value < libraryItemChannels) - { - return AudioChannels.Value; - } - } - - return null; - } - - /// - /// Gets the number of audio channels to specify on the command line - /// - protected int? GetSampleRateParam(int libraryItemSampleRate) - { - // If the user requested a max value - if (AudioSampleRate.HasValue) - { - // Only specify the param if we're going to downmix - if (AudioSampleRate.Value < libraryItemSampleRate) - { - return AudioSampleRate.Value; - } - } - - return null; - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/FavoriteStatusHandler.cs b/MediaBrowser.Api/HttpHandlers/FavoriteStatusHandler.cs deleted file mode 100644 index 19c175d8b8..0000000000 --- a/MediaBrowser.Api/HttpHandlers/FavoriteStatusHandler.cs +++ /dev/null @@ -1,38 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.DTO; -using System.ComponentModel.Composition; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - /// - /// Provides a handler to set user favorite status for an item - /// - [Export(typeof(BaseHandler))] - public class FavoriteStatusHandler : BaseSerializationHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("FavoriteStatus", request); - } - - protected override Task GetObjectToSerialize() - { - // Get the item - BaseItem item = ApiService.GetItemById(QueryString["id"]); - - // Get the user - User user = ApiService.GetUserById(QueryString["userid"], true); - - // Get the user data for this item - UserItemData data = item.GetUserData(user, true); - - // Set favorite status - data.IsFavorite = QueryString["isfavorite"] == "1"; - - return Task.FromResult(ApiService.GetDtoUserItemData(data)); - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Api/HttpHandlers/GenreHandler.cs b/MediaBrowser.Api/HttpHandlers/GenreHandler.cs deleted file mode 100644 index 7cca2aea76..0000000000 --- a/MediaBrowser.Api/HttpHandlers/GenreHandler.cs +++ /dev/null @@ -1,57 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.DTO; -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - /// - /// Gets a single genre - /// - [Export(typeof(BaseHandler))] - public class GenreHandler : BaseSerializationHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("genre", request); - } - - protected override Task GetObjectToSerialize() - { - var parent = ApiService.GetItemById(QueryString["id"]) as Folder; - var user = ApiService.GetUserById(QueryString["userid"], true); - - string name = QueryString["name"]; - - return GetGenre(parent, user, name); - } - - /// - /// Gets a Genre - /// - private async Task GetGenre(Folder parent, User user, string name) - { - int count = 0; - - // Get all the allowed recursive children - IEnumerable allItems = parent.GetRecursiveChildren(user); - - foreach (var item in allItems) - { - if (item.Genres != null && item.Genres.Any(s => s.Equals(name, StringComparison.OrdinalIgnoreCase))) - { - count++; - } - } - - // Get the original entity so that we can also supply the PrimaryImagePath - return ApiService.GetIbnItem(await Kernel.Instance.ItemController.GetGenre(name).ConfigureAwait(false), count); - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/GenresHandler.cs b/MediaBrowser.Api/HttpHandlers/GenresHandler.cs deleted file mode 100644 index 4c5a9f4b7f..0000000000 --- a/MediaBrowser.Api/HttpHandlers/GenresHandler.cs +++ /dev/null @@ -1,78 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.DTO; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - [Export(typeof(BaseHandler))] - public class GenresHandler : BaseSerializationHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("genres", request); - } - - protected override Task GetObjectToSerialize() - { - var parent = ApiService.GetItemById(QueryString["id"]) as Folder; - User user = ApiService.GetUserById(QueryString["userid"], true); - - return GetAllGenres(parent, user); - } - - /// - /// Gets all genres from all recursive children of a folder - /// The CategoryInfo class is used to keep track of the number of times each genres appears - /// - private async Task GetAllGenres(Folder parent, User user) - { - var data = new Dictionary(); - - // Get all the allowed recursive children - IEnumerable allItems = parent.GetRecursiveChildren(user); - - foreach (var item in allItems) - { - // Add each genre from the item to the data dictionary - // If the genre already exists, increment the count - if (item.Genres == null) - { - continue; - } - - foreach (string val in item.Genres) - { - if (!data.ContainsKey(val)) - { - data.Add(val, 1); - } - else - { - data[val]++; - } - } - } - - // Get the Genre objects - Genre[] entities = await Task.WhenAll(data.Keys.Select(key => Kernel.Instance.ItemController.GetGenre(key))).ConfigureAwait(false); - - // Convert to an array of IBNItem - var items = new IbnItem[entities.Length]; - - for (int i = 0; i < entities.Length; i++) - { - Genre e = entities[i]; - - items[i] = ApiService.GetIbnItem(e, data[e.Name]); - } - - return items; - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/ImageHandler.cs b/MediaBrowser.Api/HttpHandlers/ImageHandler.cs deleted file mode 100644 index 4aa367fb7e..0000000000 --- a/MediaBrowser.Api/HttpHandlers/ImageHandler.cs +++ /dev/null @@ -1,224 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Entities; -using System; -using System.ComponentModel.Composition; -using System.IO; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - [Export(typeof(BaseHandler))] - public class ImageHandler : BaseHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("image", request); - } - - private string _imagePath; - - private async Task GetImagePath() - { - _imagePath = _imagePath ?? await DiscoverImagePath(); - - return _imagePath; - } - - private BaseEntity _sourceEntity; - - private async Task GetSourceEntity() - { - if (_sourceEntity == null) - { - if (!string.IsNullOrEmpty(QueryString["personname"])) - { - _sourceEntity = - await Kernel.Instance.ItemController.GetPerson(QueryString["personname"]).ConfigureAwait(false); - } - - else if (!string.IsNullOrEmpty(QueryString["genre"])) - { - _sourceEntity = - await Kernel.Instance.ItemController.GetGenre(QueryString["genre"]).ConfigureAwait(false); - } - - else if (!string.IsNullOrEmpty(QueryString["year"])) - { - _sourceEntity = - await - Kernel.Instance.ItemController.GetYear(int.Parse(QueryString["year"])).ConfigureAwait(false); - } - - else if (!string.IsNullOrEmpty(QueryString["studio"])) - { - _sourceEntity = - await Kernel.Instance.ItemController.GetStudio(QueryString["studio"]).ConfigureAwait(false); - } - - else if (!string.IsNullOrEmpty(QueryString["userid"])) - { - _sourceEntity = ApiService.GetUserById(QueryString["userid"], false); - } - - else - { - _sourceEntity = ApiService.GetItemById(QueryString["id"]); - } - } - - return _sourceEntity; - } - - private async Task DiscoverImagePath() - { - var entity = await GetSourceEntity().ConfigureAwait(false); - - return ImageProcessor.GetImagePath(entity, ImageType, ImageIndex); - } - - protected async override Task GetResponseInfo() - { - string path = await GetImagePath().ConfigureAwait(false); - - ResponseInfo info = new ResponseInfo - { - CacheDuration = TimeSpan.FromDays(365), - ContentType = MimeTypes.GetMimeType(path) - }; - - DateTime? date = File.GetLastWriteTimeUtc(path); - - // If the file does not exist it will return jan 1, 1601 - // http://msdn.microsoft.com/en-us/library/system.io.file.getlastwritetimeutc.aspx - if (date.Value.Year == 1601) - { - if (!File.Exists(path)) - { - info.StatusCode = 404; - date = null; - } - } - - info.DateLastModified = date; - - return info; - } - - private int ImageIndex - { - get - { - string val = QueryString["index"]; - - if (string.IsNullOrEmpty(val)) - { - return 0; - } - - return int.Parse(val); - } - } - - private int? Height - { - get - { - string val = QueryString["height"]; - - if (string.IsNullOrEmpty(val)) - { - return null; - } - - return int.Parse(val); - } - } - - private int? Width - { - get - { - string val = QueryString["width"]; - - if (string.IsNullOrEmpty(val)) - { - return null; - } - - return int.Parse(val); - } - } - - private int? MaxHeight - { - get - { - string val = QueryString["maxheight"]; - - if (string.IsNullOrEmpty(val)) - { - return null; - } - - return int.Parse(val); - } - } - - private int? MaxWidth - { - get - { - string val = QueryString["maxwidth"]; - - if (string.IsNullOrEmpty(val)) - { - return null; - } - - return int.Parse(val); - } - } - - private int? Quality - { - get - { - string val = QueryString["quality"]; - - if (string.IsNullOrEmpty(val)) - { - return null; - } - - return int.Parse(val); - } - } - - private ImageType ImageType - { - get - { - string imageType = QueryString["type"]; - - if (string.IsNullOrEmpty(imageType)) - { - return ImageType.Primary; - } - - return (ImageType)Enum.Parse(typeof(ImageType), imageType, true); - } - } - - protected override async Task WriteResponseToOutputStream(Stream stream) - { - var entity = await GetSourceEntity().ConfigureAwait(false); - - ImageProcessor.ProcessImage(entity, ImageType, ImageIndex, stream, Width, Height, MaxWidth, MaxHeight, Quality); - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/ItemHandler.cs b/MediaBrowser.Api/HttpHandlers/ItemHandler.cs deleted file mode 100644 index 60b328d1a3..0000000000 --- a/MediaBrowser.Api/HttpHandlers/ItemHandler.cs +++ /dev/null @@ -1,35 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.DTO; -using System.ComponentModel.Composition; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - /// - /// Provides a handler to retrieve a single item - /// - [Export(typeof(BaseHandler))] - public class ItemHandler : BaseSerializationHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("item", request); - } - - protected override Task GetObjectToSerialize() - { - User user = ApiService.GetUserById(QueryString["userid"], true); - - BaseItem item = ApiService.GetItemById(QueryString["id"]); - - if (item == null) - { - return null; - } - - return ApiService.GetDtoBaseItem(item, user); - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/ItemListHandler.cs b/MediaBrowser.Api/HttpHandlers/ItemListHandler.cs deleted file mode 100644 index d236e546b2..0000000000 --- a/MediaBrowser.Api/HttpHandlers/ItemListHandler.cs +++ /dev/null @@ -1,84 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.DTO; -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - [Export(typeof(BaseHandler))] - public class ItemListHandler : BaseSerializationHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("itemlist", request); - } - - protected override Task GetObjectToSerialize() - { - User user = ApiService.GetUserById(QueryString["userid"], true); - - return Task.WhenAll(GetItemsToSerialize(user).Select(i => ApiService.GetDtoBaseItem(i, user, includeChildren: false, includePeople: false))); - } - - private IEnumerable GetItemsToSerialize(User user) - { - var parent = ApiService.GetItemById(ItemId) as Folder; - - if (ListType.Equals("inprogressitems", StringComparison.OrdinalIgnoreCase)) - { - return parent.GetInProgressItems(user); - } - if (ListType.Equals("recentlyaddeditems", StringComparison.OrdinalIgnoreCase)) - { - return parent.GetRecentlyAddedItems(user); - } - if (ListType.Equals("recentlyaddedunplayeditems", StringComparison.OrdinalIgnoreCase)) - { - return parent.GetRecentlyAddedUnplayedItems(user); - } - if (ListType.Equals("itemswithgenre", StringComparison.OrdinalIgnoreCase)) - { - return parent.GetItemsWithGenre(QueryString["name"], user); - } - if (ListType.Equals("itemswithyear", StringComparison.OrdinalIgnoreCase)) - { - return parent.GetItemsWithYear(int.Parse(QueryString["year"]), user); - } - if (ListType.Equals("itemswithstudio", StringComparison.OrdinalIgnoreCase)) - { - return parent.GetItemsWithStudio(QueryString["name"], user); - } - if (ListType.Equals("itemswithperson", StringComparison.OrdinalIgnoreCase)) - { - return parent.GetItemsWithPerson(QueryString["name"], null, user); - } - if (ListType.Equals("favorites", StringComparison.OrdinalIgnoreCase)) - { - return parent.GetFavoriteItems(user); - } - - throw new InvalidOperationException(); - } - - protected string ItemId - { - get - { - return QueryString["id"]; - } - } - - private string ListType - { - get - { - return QueryString["listtype"] ?? string.Empty; - } - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/MovieSpecialFeaturesHandler.cs b/MediaBrowser.Api/HttpHandlers/MovieSpecialFeaturesHandler.cs deleted file mode 100644 index 3ab78ee8d1..0000000000 --- a/MediaBrowser.Api/HttpHandlers/MovieSpecialFeaturesHandler.cs +++ /dev/null @@ -1,46 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Model.DTO; -using System.ComponentModel.Composition; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - /// - /// This handler retrieves special features for movies - /// - [Export(typeof(BaseHandler))] - public class MovieSpecialFeaturesHandler : BaseSerializationHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("MovieSpecialFeatures", request); - } - - protected override Task GetObjectToSerialize() - { - User user = ApiService.GetUserById(QueryString["userid"], true); - - var movie = ApiService.GetItemById(ItemId) as Movie; - - // If none - if (movie.SpecialFeatures == null) - { - return Task.FromResult(new DtoBaseItem[] { }); - } - - return Task.WhenAll(movie.SpecialFeatures.Select(i => ApiService.GetDtoBaseItem(i, user, includeChildren: false))); - } - - protected string ItemId - { - get - { - return QueryString["id"]; - } - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/PersonHandler.cs b/MediaBrowser.Api/HttpHandlers/PersonHandler.cs deleted file mode 100644 index fbbd88a11d..0000000000 --- a/MediaBrowser.Api/HttpHandlers/PersonHandler.cs +++ /dev/null @@ -1,55 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.DTO; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - /// - /// Gets a single Person - /// - [Export(typeof(BaseHandler))] - public class PersonHandler : BaseSerializationHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("person", request); - } - - protected override Task GetObjectToSerialize() - { - var parent = ApiService.GetItemById(QueryString["id"]) as Folder; - var user = ApiService.GetUserById(QueryString["userid"], true); - - string name = QueryString["name"]; - - return GetPerson(parent, user, name); - } - - /// - /// Gets a Person - /// - private async Task GetPerson(Folder parent, User user, string name) - { - int count = 0; - - // Get all the allowed recursive children - IEnumerable allItems = parent.GetRecursiveChildren(user); - - foreach (var item in allItems) - { - if (item.People != null && item.People.ContainsKey(name)) - { - count++; - } - } - - // Get the original entity so that we can also supply the PrimaryImagePath - return ApiService.GetIbnItem(await Kernel.Instance.ItemController.GetPerson(name).ConfigureAwait(false), count); - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/PlaybackCheckInHandler.cs b/MediaBrowser.Api/HttpHandlers/PlaybackCheckInHandler.cs new file mode 100644 index 0000000000..c6f11e5bbc --- /dev/null +++ b/MediaBrowser.Api/HttpHandlers/PlaybackCheckInHandler.cs @@ -0,0 +1,112 @@ +using MediaBrowser.Common.Net.Handlers; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Connectivity; +using MediaBrowser.Model.DTO; +using System; +using System.ComponentModel.Composition; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.HttpHandlers +{ + /// + /// Provides a handler to set played status for an item + /// + [Export(typeof(IHttpServerHandler))] + public class PlaybackCheckInHandler : BaseSerializationHandler + { + /// + /// Gets the object to serialize. + /// + /// Task{DtoUserItemData}. + protected override async Task GetObjectToSerialize() + { + // Get the user + var user = await this.GetCurrentUser().ConfigureAwait(false); + + var clientType = ClientType.Other; + + if (!string.IsNullOrEmpty(QueryString["client"])) + { + ClientType type; + + if (Enum.TryParse(QueryString["client"], true, out type)) + { + clientType = type; + } + } + + var device = QueryString["device"]; + + // Get the item + var item = DtoBuilder.GetItemByClientId(QueryString["id"], user.Id); + + // Playback start check-in + if (QueryString["type"].Equals("start", StringComparison.OrdinalIgnoreCase)) + { + Kernel.UserDataManager.OnPlaybackStart(user, item, clientType, device); + } + else + { + long? positionTicks = null; + + if (!string.IsNullOrEmpty(QueryString["positionTicks"])) + { + positionTicks = long.Parse(QueryString["positionTicks"]); + } + + // Progress check-ins require position ticks + if (QueryString["type"].Equals("progress", StringComparison.OrdinalIgnoreCase)) + { + await Kernel.UserDataManager.OnPlaybackProgress(user, item, positionTicks, clientType, device).ConfigureAwait(false); + } + else if (QueryString["type"].Equals("stopped", StringComparison.OrdinalIgnoreCase)) + { + await Kernel.UserDataManager.OnPlaybackStopped(user, item, positionTicks, clientType, device).ConfigureAwait(false); + } + } + + var data = item.GetUserData(user, true); + + return DtoBuilder.GetDtoUserItemData(data); + } + + /// + /// Gets the current user. + /// + /// User. + /// + public async Task GetCurrentUser() + { + var handler = this; + var id = handler.QueryString["userid"]; + + var user = ApiService.GetUserById(id); + + if (user == null) + { + throw new UnauthorizedAccessException(string.Format("User with Id {0} does not exist", id)); + } + + var clientType = ClientType.Other; + + if (!string.IsNullOrEmpty(handler.QueryString["client"])) + { + ClientType type; + + if (Enum.TryParse(handler.QueryString["client"], true, out type)) + { + clientType = type; + } + } + + var device = handler.QueryString["device"]; + + await Controller.Kernel.Instance.UserManager.LogUserActivity(user, clientType, device).ConfigureAwait(false); + + return user; + } + + } +} \ No newline at end of file diff --git a/MediaBrowser.Api/HttpHandlers/PlayedStatusHandler.cs b/MediaBrowser.Api/HttpHandlers/PlayedStatusHandler.cs deleted file mode 100644 index c010bcb023..0000000000 --- a/MediaBrowser.Api/HttpHandlers/PlayedStatusHandler.cs +++ /dev/null @@ -1,38 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.DTO; -using System.ComponentModel.Composition; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - /// - /// Provides a handler to set played status for an item - /// - [Export(typeof(BaseHandler))] - public class PlayedStatusHandler : BaseSerializationHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("PlayedStatus", request); - } - - protected override Task GetObjectToSerialize() - { - // Get the item - BaseItem item = ApiService.GetItemById(QueryString["id"]); - - // Get the user - User user = ApiService.GetUserById(QueryString["userid"], true); - - bool wasPlayed = QueryString["played"] == "1"; - - item.SetPlayedStatus(user, wasPlayed); - - UserItemData data = item.GetUserData(user, true); - - return Task.FromResult(ApiService.GetDtoUserItemData(data)); - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Api/HttpHandlers/PluginAssemblyHandler.cs b/MediaBrowser.Api/HttpHandlers/PluginAssemblyHandler.cs deleted file mode 100644 index 47f08c8c32..0000000000 --- a/MediaBrowser.Api/HttpHandlers/PluginAssemblyHandler.cs +++ /dev/null @@ -1,38 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller; -using System; -using System.ComponentModel.Composition; -using System.IO; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - [Export(typeof(BaseHandler))] - class PluginAssemblyHandler : BaseHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("pluginassembly", request); - } - - protected override Task GetResponseInfo() - { - throw new NotImplementedException(); - } - - protected override Task WriteResponseToOutputStream(Stream stream) - { - throw new NotImplementedException(); - } - - public override Task ProcessRequest(HttpListenerContext ctx) - { - string filename = ctx.Request.QueryString["assemblyfilename"]; - - string path = Path.Combine(Kernel.Instance.ApplicationPaths.PluginsPath, filename); - - return new StaticFileHandler { Path = path }.ProcessRequest(ctx); - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/PluginConfigurationHandler.cs b/MediaBrowser.Api/HttpHandlers/PluginConfigurationHandler.cs deleted file mode 100644 index dc363956fd..0000000000 --- a/MediaBrowser.Api/HttpHandlers/PluginConfigurationHandler.cs +++ /dev/null @@ -1,53 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Common.Plugins; -using MediaBrowser.Controller; -using MediaBrowser.Model.Plugins; -using System; -using System.ComponentModel.Composition; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - [Export(typeof(BaseHandler))] - public class PluginConfigurationHandler : BaseSerializationHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("pluginconfiguration", request); - } - - private BasePlugin _plugin; - private BasePlugin Plugin - { - get - { - if (_plugin == null) - { - string name = QueryString["assemblyfilename"]; - - _plugin = Kernel.Instance.Plugins.First(p => p.AssemblyFileName.Equals(name, StringComparison.OrdinalIgnoreCase)); - } - - return _plugin; - } - } - - protected override Task GetObjectToSerialize() - { - return Task.FromResult(Plugin.Configuration); - } - - protected override async Task GetResponseInfo() - { - var info = await base.GetResponseInfo().ConfigureAwait(false); - - info.DateLastModified = Plugin.ConfigurationDateLastModified; - - info.CacheDuration = TimeSpan.FromDays(7); - - return info; - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/PluginsHandler.cs b/MediaBrowser.Api/HttpHandlers/PluginsHandler.cs deleted file mode 100644 index a1b37ecaba..0000000000 --- a/MediaBrowser.Api/HttpHandlers/PluginsHandler.cs +++ /dev/null @@ -1,38 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller; -using MediaBrowser.Model.DTO; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - /// - /// Provides information about installed plugins - /// - [Export(typeof(BaseHandler))] - public class PluginsHandler : BaseSerializationHandler> - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("plugins", request); - } - - protected override Task> GetObjectToSerialize() - { - var plugins = Kernel.Instance.Plugins.Select(p => new PluginInfo - { - Name = p.Name, - Enabled = p.Enabled, - DownloadToUI = p.DownloadToUi, - Version = p.Version.ToString(), - AssemblyFileName = p.AssemblyFileName, - ConfigurationDateLastModified = p.ConfigurationDateLastModified - }); - - return Task.FromResult(plugins); - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/ServerConfigurationHandler.cs b/MediaBrowser.Api/HttpHandlers/ServerConfigurationHandler.cs deleted file mode 100644 index 48c6761b16..0000000000 --- a/MediaBrowser.Api/HttpHandlers/ServerConfigurationHandler.cs +++ /dev/null @@ -1,37 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller; -using MediaBrowser.Model.Configuration; -using System; -using System.ComponentModel.Composition; -using System.IO; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - [Export(typeof(BaseHandler))] - class ServerConfigurationHandler : BaseSerializationHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("serverconfiguration", request); - } - - protected override Task GetObjectToSerialize() - { - return Task.FromResult(Kernel.Instance.Configuration); - } - - protected override async Task GetResponseInfo() - { - var info = await base.GetResponseInfo().ConfigureAwait(false); - - info.DateLastModified = - File.GetLastWriteTimeUtc(Kernel.Instance.ApplicationPaths.SystemConfigurationFilePath); - - info.CacheDuration = TimeSpan.FromDays(7); - - return info; - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/StudioHandler.cs b/MediaBrowser.Api/HttpHandlers/StudioHandler.cs deleted file mode 100644 index 6576e2cfe5..0000000000 --- a/MediaBrowser.Api/HttpHandlers/StudioHandler.cs +++ /dev/null @@ -1,57 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.DTO; -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - /// - /// Gets a single studio - /// - [Export(typeof(BaseHandler))] - public class StudioHandler : BaseSerializationHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("studio", request); - } - - protected override Task GetObjectToSerialize() - { - var parent = ApiService.GetItemById(QueryString["id"]) as Folder; - var user = ApiService.GetUserById(QueryString["userid"], true); - - string name = QueryString["name"]; - - return GetStudio(parent, user, name); - } - - /// - /// Gets a Studio - /// - private async Task GetStudio(Folder parent, User user, string name) - { - int count = 0; - - // Get all the allowed recursive children - IEnumerable allItems = parent.GetRecursiveChildren(user); - - foreach (var item in allItems) - { - if (item.Studios != null && item.Studios.Any(s => s.Equals(name, StringComparison.OrdinalIgnoreCase))) - { - count++; - } - } - - // Get the original entity so that we can also supply the PrimaryImagePath - return ApiService.GetIbnItem(await Kernel.Instance.ItemController.GetStudio(name).ConfigureAwait(false), count); - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/StudiosHandler.cs b/MediaBrowser.Api/HttpHandlers/StudiosHandler.cs deleted file mode 100644 index 4377a0f432..0000000000 --- a/MediaBrowser.Api/HttpHandlers/StudiosHandler.cs +++ /dev/null @@ -1,78 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.DTO; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - [Export(typeof(BaseHandler))] - public class StudiosHandler : BaseSerializationHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("studios", request); - } - - protected override Task GetObjectToSerialize() - { - var parent = ApiService.GetItemById(QueryString["id"]) as Folder; - var user = ApiService.GetUserById(QueryString["userid"], true); - - return GetAllStudios(parent, user); - } - - /// - /// Gets all studios from all recursive children of a folder - /// The CategoryInfo class is used to keep track of the number of times each studio appears - /// - private async Task GetAllStudios(Folder parent, User user) - { - var data = new Dictionary(); - - // Get all the allowed recursive children - IEnumerable allItems = parent.GetRecursiveChildren(user); - - foreach (var item in allItems) - { - // Add each studio from the item to the data dictionary - // If the studio already exists, increment the count - if (item.Studios == null) - { - continue; - } - - foreach (string val in item.Studios) - { - if (!data.ContainsKey(val)) - { - data.Add(val, 1); - } - else - { - data[val]++; - } - } - } - - // Get the Studio objects - Studio[] entities = await Task.WhenAll(data.Keys.Select(key => Kernel.Instance.ItemController.GetStudio(key))).ConfigureAwait(false); - - // Convert to an array of IBNItem - var items = new IbnItem[entities.Length]; - - for (int i = 0; i < entities.Length; i++) - { - Studio e = entities[i]; - - items[i] = ApiService.GetIbnItem(e, data[e.Name]); - } - - return items; - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/UpdateMediaLibraryHandler.cs b/MediaBrowser.Api/HttpHandlers/UpdateMediaLibraryHandler.cs new file mode 100644 index 0000000000..e5c42008e1 --- /dev/null +++ b/MediaBrowser.Api/HttpHandlers/UpdateMediaLibraryHandler.cs @@ -0,0 +1,263 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net.Handlers; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using System; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.HttpHandlers +{ + /// + /// Makes changes to the user's media library + /// + [Export(typeof(IHttpServerHandler))] + public class UpdateMediaLibraryHandler : BaseActionHandler + { + /// + /// Executes the action. + /// + /// Task. + /// + protected override Task ExecuteAction() + { + return Task.Run(() => + { + var action = QueryString["action"]; + + if (string.IsNullOrEmpty(action)) + { + throw new ArgumentNullException(); + } + + User user = null; + + if (!string.IsNullOrEmpty(QueryString["userId"])) + { + user = ApiService.GetUserById(QueryString["userId"]); + } + + if (action.Equals("AddVirtualFolder", StringComparison.OrdinalIgnoreCase)) + { + AddVirtualFolder(Uri.UnescapeDataString(QueryString["name"]), user); + } + + if (action.Equals("RemoveVirtualFolder", StringComparison.OrdinalIgnoreCase)) + { + RemoveVirtualFolder(QueryString["name"], user); + } + + if (action.Equals("RenameVirtualFolder", StringComparison.OrdinalIgnoreCase)) + { + RenameVirtualFolder(QueryString["name"], QueryString["newName"], user); + } + + if (action.Equals("RemoveMediaPath", StringComparison.OrdinalIgnoreCase)) + { + RemoveMediaPath(QueryString["virtualFolderName"], QueryString["mediaPath"], user); + } + + if (action.Equals("AddMediaPath", StringComparison.OrdinalIgnoreCase)) + { + AddMediaPath(QueryString["virtualFolderName"], QueryString["mediaPath"], user); + } + + throw new ArgumentOutOfRangeException(); + }); + } + + /// + /// Adds a virtual folder to either the default view or a user view + /// + /// The name. + /// The user. + private void AddVirtualFolder(string name, User user) + { + name = FileSystem.GetValidFilename(name); + + var rootFolderPath = user != null ? user.RootFolderPath : Kernel.ApplicationPaths.DefaultUserViewsPath; + var virtualFolderPath = Path.Combine(rootFolderPath, name); + + if (Directory.Exists(virtualFolderPath)) + { + throw new ArgumentException("There is already a media collection with the name " + name + "."); + } + + Directory.CreateDirectory(virtualFolderPath); + } + + /// + /// Adds an additional mediaPath to an existing virtual folder, within either the default view or a user view + /// + /// Name of the virtual folder. + /// The path. + /// The user. + private void AddMediaPath(string virtualFolderName, string path, User user) + { + if (!Path.IsPathRooted(path)) + { + throw new ArgumentException("The path is not valid."); + } + + if (!Directory.Exists(path)) + { + throw new DirectoryNotFoundException("The path does not exist."); + } + + // Strip off trailing slash, but not on drives + path = path.TrimEnd(Path.DirectorySeparatorChar); + if (path.EndsWith(":", StringComparison.OrdinalIgnoreCase)) + { + path += Path.DirectorySeparatorChar; + } + + var rootFolderPath = user != null ? user.RootFolderPath : Kernel.ApplicationPaths.DefaultUserViewsPath; + var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName); + + ValidateNewMediaPath(rootFolderPath, path); + + var shortcutFilename = Path.GetFileNameWithoutExtension(path); + + var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ".lnk"); + + while (File.Exists(lnk)) + { + shortcutFilename += "1"; + lnk = Path.Combine(virtualFolderPath, shortcutFilename + ".lnk"); + } + + FileSystem.CreateShortcut(lnk, path); + } + + /// + /// Validates that a new media path can be added + /// + /// The current view root folder path. + /// The media path. + private void ValidateNewMediaPath(string currentViewRootFolderPath, string mediaPath) + { + var duplicate = Directory.EnumerateFiles(Kernel.ApplicationPaths.RootFolderPath, "*.lnk", SearchOption.AllDirectories) + .Select(FileSystem.ResolveShortcut) + .FirstOrDefault(p => !IsNewPathValid(mediaPath, p)); + + if (!string.IsNullOrEmpty(duplicate)) + { + throw new ArgumentException(string.Format("The path cannot be added to the library because {0} already exists.", duplicate)); + } + + // Make sure the current root folder doesn't already have a shortcut to the same path + duplicate = Directory.EnumerateFiles(currentViewRootFolderPath, "*.lnk", SearchOption.AllDirectories) + .Select(FileSystem.ResolveShortcut) + .FirstOrDefault(p => mediaPath.Equals(p, StringComparison.OrdinalIgnoreCase)); + + if (!string.IsNullOrEmpty(duplicate)) + { + throw new ArgumentException(string.Format("The path {0} already exists in the library", mediaPath)); + } + } + + /// + /// Validates that a new path can be added based on an existing path + /// + /// The new path. + /// The existing path. + /// true if [is new path valid] [the specified new path]; otherwise, false. + private bool IsNewPathValid(string newPath, string existingPath) + { + // Example: D:\Movies is the existing path + // D:\ cannot be added + // Neither can D:\Movies\Kids + // A D:\Movies duplicate is ok here since that will be caught later + + if (newPath.Equals(existingPath, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + // Validate the D:\Movies\Kids scenario + if (newPath.StartsWith(existingPath.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + // Validate the D:\ scenario + if (existingPath.StartsWith(newPath.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + return true; + } + + /// + /// Renames a virtual folder within either the default view or a user view + /// + /// The name. + /// The new name. + /// The user. + private void RenameVirtualFolder(string name, string newName, User user) + { + var rootFolderPath = user != null ? user.RootFolderPath : Kernel.ApplicationPaths.DefaultUserViewsPath; + + var currentPath = Path.Combine(rootFolderPath, name); + var newPath = Path.Combine(rootFolderPath, newName); + + if (!Directory.Exists(currentPath)) + { + throw new DirectoryNotFoundException("The media collection does not exist"); + } + + if (Directory.Exists(newPath)) + { + throw new ArgumentException("There is already a media collection with the name " + newPath + "."); + } + + Directory.Move(currentPath, newPath); + } + + /// + /// Deletes a virtual folder from either the default view or a user view + /// + /// The name. + /// The user. + private void RemoveVirtualFolder(string name, User user) + { + var rootFolderPath = user != null ? user.RootFolderPath : Kernel.ApplicationPaths.DefaultUserViewsPath; + var path = Path.Combine(rootFolderPath, name); + + if (!Directory.Exists(path)) + { + throw new DirectoryNotFoundException("The media folder does not exist"); + } + + Directory.Delete(path, true); + } + + /// + /// Deletes a shortcut from within a virtual folder, within either the default view or a user view + /// + /// Name of the virtual folder. + /// The media path. + /// The user. + private void RemoveMediaPath(string virtualFolderName, string mediaPath, User user) + { + var rootFolderPath = user != null ? user.RootFolderPath : Kernel.ApplicationPaths.DefaultUserViewsPath; + var path = Path.Combine(rootFolderPath, virtualFolderName); + + if (!Directory.Exists(path)) + { + throw new DirectoryNotFoundException("The media folder does not exist"); + } + + var shortcut = Directory.EnumerateFiles(path, "*.lnk", SearchOption.AllDirectories).FirstOrDefault(f => FileSystem.ResolveShortcut(f).Equals(mediaPath, StringComparison.OrdinalIgnoreCase)); + + if (string.IsNullOrEmpty(shortcut)) + { + throw new DirectoryNotFoundException("The media folder does not exist"); + } + File.Delete(shortcut); + } + } +} diff --git a/MediaBrowser.Api/HttpHandlers/UserAuthenticationHandler.cs b/MediaBrowser.Api/HttpHandlers/UserAuthenticationHandler.cs deleted file mode 100644 index fa9d975983..0000000000 --- a/MediaBrowser.Api/HttpHandlers/UserAuthenticationHandler.cs +++ /dev/null @@ -1,29 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Authentication; -using System.ComponentModel.Composition; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - [Export(typeof(BaseHandler))] - class UserAuthenticationHandler : BaseSerializationHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("UserAuthentication", request); - } - - protected override async Task GetObjectToSerialize() - { - string userId = await GetFormValue("userid").ConfigureAwait(false); - User user = ApiService.GetUserById(userId, false); - - string password = await GetFormValue("password").ConfigureAwait(false); - - return Kernel.Instance.AuthenticateUser(user, password); - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/UserHandler.cs b/MediaBrowser.Api/HttpHandlers/UserHandler.cs deleted file mode 100644 index bc92862040..0000000000 --- a/MediaBrowser.Api/HttpHandlers/UserHandler.cs +++ /dev/null @@ -1,29 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.DTO; -using System.ComponentModel.Composition; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - [Export(typeof(BaseHandler))] - class UserHandler : BaseSerializationHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("user", request); - } - - protected override Task GetObjectToSerialize() - { - string id = QueryString["id"]; - - User user = string.IsNullOrEmpty(id) ? ApiService.GetDefaultUser(false) : ApiService.GetUserById(id, false); - - DtoUser dto = ApiService.GetDtoUser(user); - - return Task.FromResult(dto); - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/UserItemRatingHandler.cs b/MediaBrowser.Api/HttpHandlers/UserItemRatingHandler.cs deleted file mode 100644 index aed0804b6d..0000000000 --- a/MediaBrowser.Api/HttpHandlers/UserItemRatingHandler.cs +++ /dev/null @@ -1,46 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.DTO; -using System.ComponentModel.Composition; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - /// - /// Provides a handler to set a user's rating for an item - /// - [Export(typeof(BaseHandler))] - public class UserItemRatingHandler : BaseSerializationHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("UserItemRating", request); - } - - protected override Task GetObjectToSerialize() - { - // Get the item - BaseItem item = ApiService.GetItemById(QueryString["id"]); - - // Get the user - User user = ApiService.GetUserById(QueryString["userid"], true); - - // Get the user data for this item - UserItemData data = item.GetUserData(user, true); - - // If clearing the rating, set it to null - if (QueryString["clear"] == "1") - { - data.Rating = null; - } - - else - { - data.Likes = QueryString["likes"] == "1"; - } - - return Task.FromResult(ApiService.GetDtoUserItemData(data)); - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Api/HttpHandlers/UsersHandler.cs b/MediaBrowser.Api/HttpHandlers/UsersHandler.cs deleted file mode 100644 index 3fc3a7d585..0000000000 --- a/MediaBrowser.Api/HttpHandlers/UsersHandler.cs +++ /dev/null @@ -1,25 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller; -using MediaBrowser.Model.DTO; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - [Export(typeof(BaseHandler))] - class UsersHandler : BaseSerializationHandler> - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("users", request); - } - - protected override Task> GetObjectToSerialize() - { - return Task.FromResult(Kernel.Instance.Users.Select(u => ApiService.GetDtoUser(u))); - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/VideoHandler.cs b/MediaBrowser.Api/HttpHandlers/VideoHandler.cs deleted file mode 100644 index e34a1b41f7..0000000000 --- a/MediaBrowser.Api/HttpHandlers/VideoHandler.cs +++ /dev/null @@ -1,424 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.DTO; -using MediaBrowser.Model.Entities; -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Drawing; -using System.Linq; -using System.Net; - -namespace MediaBrowser.Api.HttpHandlers -{ - /// - /// Supported output formats: mkv,m4v,mp4,asf,wmv,mov,webm,ogv,3gp,avi,ts,flv - /// - [Export(typeof(BaseHandler))] - class VideoHandler : BaseMediaHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("video", request); - } - - /// - /// We can output these files directly, but we can't encode them - /// - protected override IEnumerable UnsupportedOutputEncodingFormats - { - get - { - // mp4, 3gp, mov - muxer does not support non-seekable output - // avi, mov, mkv, m4v - can't stream these when encoding. the player will try to download them completely before starting playback. - // wmv - can't seem to figure out the output format name - return new VideoOutputFormats[] { VideoOutputFormats.Mp4, VideoOutputFormats.ThreeGp, VideoOutputFormats.M4V, VideoOutputFormats.Mkv, VideoOutputFormats.Avi, VideoOutputFormats.Mov, VideoOutputFormats.Wmv }; - } - } - - /// - /// Determines whether or not we can just output the original file directly - /// - protected override bool RequiresConversion() - { - if (base.RequiresConversion()) - { - return true; - } - - // See if the video requires conversion - if (RequiresVideoConversion()) - { - return true; - } - - // See if the audio requires conversion - AudioStream audioStream = (LibraryItem.AudioStreams ?? new List()).FirstOrDefault(); - - if (audioStream != null) - { - if (RequiresAudioConversion(audioStream)) - { - return true; - } - } - - // Yay - return false; - } - - /// - /// Translates the output file extension to the format param that follows "-f" on the ffmpeg command line - /// - private string GetFfMpegOutputFormat(VideoOutputFormats outputFormat) - { - if (outputFormat == VideoOutputFormats.Mkv) - { - return "matroska"; - } - if (outputFormat == VideoOutputFormats.Ts) - { - return "mpegts"; - } - if (outputFormat == VideoOutputFormats.Ogv) - { - return "ogg"; - } - - return outputFormat.ToString().ToLower(); - } - - /// - /// Creates arguments to pass to ffmpeg - /// - protected override string GetCommandLineArguments() - { - VideoOutputFormats outputFormat = GetConversionOutputFormat(); - - return string.Format("-i \"{0}\" -threads 0 {1} {2} -f {3} -", - LibraryItem.Path, - GetVideoArguments(outputFormat), - GetAudioArguments(outputFormat), - GetFfMpegOutputFormat(outputFormat) - ); - } - - /// - /// Gets video arguments to pass to ffmpeg - /// - private string GetVideoArguments(VideoOutputFormats outputFormat) - { - // Get the output codec name - string codec = GetVideoCodec(outputFormat); - - string args = "-vcodec " + codec; - - // If we're encoding video, add additional params - if (!codec.Equals("copy", StringComparison.OrdinalIgnoreCase)) - { - // Add resolution params, if specified - if (Width.HasValue || Height.HasValue || MaxHeight.HasValue || MaxWidth.HasValue) - { - Size size = DrawingUtils.Resize(LibraryItem.Width, LibraryItem.Height, Width, Height, MaxWidth, MaxHeight); - - args += string.Format(" -s {0}x{1}", size.Width, size.Height); - } - } - - return args; - } - - /// - /// Gets audio arguments to pass to ffmpeg - /// - private string GetAudioArguments(VideoOutputFormats outputFormat) - { - AudioStream audioStream = (LibraryItem.AudioStreams ?? new List()).FirstOrDefault(); - - // If the video doesn't have an audio stream, return empty - if (audioStream == null) - { - return string.Empty; - } - - // Get the output codec name - string codec = GetAudioCodec(audioStream, outputFormat); - - string args = "-acodec " + codec; - - // If we're encoding audio, add additional params - if (!codec.Equals("copy", StringComparison.OrdinalIgnoreCase)) - { - // Add the number of audio channels - int? channels = GetNumAudioChannelsParam(codec, audioStream.Channels); - - if (channels.HasValue) - { - args += " -ac " + channels.Value; - } - - // Add the audio sample rate - int? sampleRate = GetSampleRateParam(audioStream.SampleRate); - - if (sampleRate.HasValue) - { - args += " -ar " + sampleRate.Value; - } - - } - - return args; - } - - /// - /// Gets the name of the output video codec - /// - private string GetVideoCodec(VideoOutputFormats outputFormat) - { - // Some output containers require specific codecs - - if (outputFormat == VideoOutputFormats.Webm) - { - // Per webm specification, it must be vpx - return "libvpx"; - } - if (outputFormat == VideoOutputFormats.Asf) - { - return "wmv2"; - } - if (outputFormat == VideoOutputFormats.Wmv) - { - return "wmv2"; - } - if (outputFormat == VideoOutputFormats.Ogv) - { - return "libtheora"; - } - - // Skip encoding when possible - if (!RequiresVideoConversion()) - { - return "copy"; - } - - return "libx264"; - } - - /// - /// Gets the name of the output audio codec - /// - private string GetAudioCodec(AudioStream audioStream, VideoOutputFormats outputFormat) - { - // Some output containers require specific codecs - - if (outputFormat == VideoOutputFormats.Webm) - { - // Per webm specification, it must be vorbis - return "libvorbis"; - } - if (outputFormat == VideoOutputFormats.Asf) - { - return "wmav2"; - } - if (outputFormat == VideoOutputFormats.Wmv) - { - return "wmav2"; - } - if (outputFormat == VideoOutputFormats.Ogv) - { - return "libvorbis"; - } - - // Skip encoding when possible - if (!RequiresAudioConversion(audioStream)) - { - return "copy"; - } - - return "libvo_aacenc"; - } - - /// - /// Gets the number of audio channels to specify on the command line - /// - private int? GetNumAudioChannelsParam(string audioCodec, int libraryItemChannels) - { - if (libraryItemChannels > 2) - { - if (audioCodec.Equals("libvo_aacenc")) - { - // libvo_aacenc currently only supports two channel output - return 2; - } - if (audioCodec.Equals("wmav2")) - { - // wmav2 currently only supports two channel output - return 2; - } - } - - return GetNumAudioChannelsParam(libraryItemChannels); - } - - /// - /// Determines if the video stream requires encoding - /// - private bool RequiresVideoConversion() - { - // Check dimensions - - // If a specific width is required, validate that - if (Width.HasValue) - { - if (Width.Value != LibraryItem.Width) - { - return true; - } - } - - // If a specific height is required, validate that - if (Height.HasValue) - { - if (Height.Value != LibraryItem.Height) - { - return true; - } - } - - // If a max width is required, validate that - if (MaxWidth.HasValue) - { - if (MaxWidth.Value < LibraryItem.Width) - { - return true; - } - } - - // If a max height is required, validate that - if (MaxHeight.HasValue) - { - if (MaxHeight.Value < LibraryItem.Height) - { - return true; - } - } - - // If the codec is already h264, don't encode - if (LibraryItem.Codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 || LibraryItem.Codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1) - { - return false; - } - - return false; - } - - /// - /// Determines if the audio stream requires encoding - /// - private bool RequiresAudioConversion(AudioStream audio) - { - - // If the input stream has more audio channels than the client can handle, we need to encode - if (AudioChannels.HasValue) - { - if (audio.Channels > AudioChannels.Value) - { - return true; - } - } - - // Aac, ac-3 and mp3 are all pretty much universally supported. No need to encode them - - if (audio.Codec.IndexOf("aac", StringComparison.OrdinalIgnoreCase) != -1) - { - return false; - } - - if (audio.Codec.IndexOf("ac-3", StringComparison.OrdinalIgnoreCase) != -1 || audio.Codec.IndexOf("ac3", StringComparison.OrdinalIgnoreCase) != -1) - { - return false; - } - - if (audio.Codec.IndexOf("mpeg", StringComparison.OrdinalIgnoreCase) != -1 || audio.Codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1) - { - return false; - } - - return true; - } - - /// - /// Gets the fixed output video height, in pixels - /// - private int? Height - { - get - { - string val = QueryString["height"]; - - if (string.IsNullOrEmpty(val)) - { - return null; - } - - return int.Parse(val); - } - } - - /// - /// Gets the fixed output video width, in pixels - /// - private int? Width - { - get - { - string val = QueryString["width"]; - - if (string.IsNullOrEmpty(val)) - { - return null; - } - - return int.Parse(val); - } - } - - /// - /// Gets the maximum output video height, in pixels - /// - private int? MaxHeight - { - get - { - string val = QueryString["maxheight"]; - - if (string.IsNullOrEmpty(val)) - { - return null; - } - - return int.Parse(val); - } - } - - /// - /// Gets the maximum output video width, in pixels - /// - private int? MaxWidth - { - get - { - string val = QueryString["maxwidth"]; - - if (string.IsNullOrEmpty(val)) - { - return null; - } - - return int.Parse(val); - } - } - - } -} diff --git a/MediaBrowser.Api/HttpHandlers/WeatherHandler.cs b/MediaBrowser.Api/HttpHandlers/WeatherHandler.cs deleted file mode 100644 index 378e89067d..0000000000 --- a/MediaBrowser.Api/HttpHandlers/WeatherHandler.cs +++ /dev/null @@ -1,43 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller; -using MediaBrowser.Model.Weather; -using System; -using System.ComponentModel.Composition; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - [Export(typeof(BaseHandler))] - class WeatherHandler : BaseSerializationHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("weather", request); - } - - protected override Task GetObjectToSerialize() - { - // If a specific zip code was requested on the query string, use that. Otherwise use the value from configuration - - string zipCode = QueryString["zipcode"]; - - if (string.IsNullOrWhiteSpace(zipCode)) - { - zipCode = Kernel.Instance.Configuration.WeatherZipCode; - } - - return Kernel.Instance.WeatherProviders.First().GetWeatherInfoAsync(zipCode); - } - - protected override async Task GetResponseInfo() - { - var info = await base.GetResponseInfo().ConfigureAwait(false); - - info.CacheDuration = TimeSpan.FromMinutes(15); - - return info; - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/YearHandler.cs b/MediaBrowser.Api/HttpHandlers/YearHandler.cs deleted file mode 100644 index dbd1d25be0..0000000000 --- a/MediaBrowser.Api/HttpHandlers/YearHandler.cs +++ /dev/null @@ -1,55 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.DTO; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - /// - /// Gets a single year - /// - [Export(typeof(BaseHandler))] - public class YearHandler : BaseSerializationHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("year", request); - } - - protected override Task GetObjectToSerialize() - { - var parent = ApiService.GetItemById(QueryString["id"]) as Folder; - var user = ApiService.GetUserById(QueryString["userid"], true); - - string year = QueryString["year"]; - - return GetYear(parent, user, int.Parse(year)); - } - - /// - /// Gets a Year - /// - private async Task GetYear(Folder parent, User user, int year) - { - int count = 0; - - // Get all the allowed recursive children - IEnumerable allItems = parent.GetRecursiveChildren(user); - - foreach (var item in allItems) - { - if (item.ProductionYear.HasValue && item.ProductionYear.Value == year) - { - count++; - } - } - - // Get the original entity so that we can also supply the PrimaryImagePath - return ApiService.GetIbnItem(await Kernel.Instance.ItemController.GetYear(year).ConfigureAwait(false), count); - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/YearsHandler.cs b/MediaBrowser.Api/HttpHandlers/YearsHandler.cs deleted file mode 100644 index 7c90768e84..0000000000 --- a/MediaBrowser.Api/HttpHandlers/YearsHandler.cs +++ /dev/null @@ -1,75 +0,0 @@ -using MediaBrowser.Common.Net.Handlers; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.DTO; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.HttpHandlers -{ - [Export(typeof(BaseHandler))] - public class YearsHandler : BaseSerializationHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return ApiService.IsApiUrlMatch("years", request); - } - - protected override Task GetObjectToSerialize() - { - var parent = ApiService.GetItemById(QueryString["id"]) as Folder; - User user = ApiService.GetUserById(QueryString["userid"], true); - - return GetAllYears(parent, user); - } - - /// - /// Gets all years from all recursive children of a folder - /// The CategoryInfo class is used to keep track of the number of times each year appears - /// - private async Task GetAllYears(Folder parent, User user) - { - var data = new Dictionary(); - - // Get all the allowed recursive children - IEnumerable allItems = parent.GetRecursiveChildren(user); - - foreach (var item in allItems) - { - // Add the year from the item to the data dictionary - // If the year already exists, increment the count - if (item.ProductionYear == null) - { - continue; - } - - if (!data.ContainsKey(item.ProductionYear.Value)) - { - data.Add(item.ProductionYear.Value, 1); - } - else - { - data[item.ProductionYear.Value]++; - } - } - - // Get the Year objects - Year[] entities = await Task.WhenAll(data.Keys.Select(key => Kernel.Instance.ItemController.GetYear(key))).ConfigureAwait(false); - - // Convert to an array of IBNItem - var items = new IbnItem[entities.Length]; - - for (int i = 0; i < entities.Length; i++) - { - Year e = entities[i]; - - items[i] = ApiService.GetIbnItem(e, data[int.Parse(e.Name)]); - } - - return items; - } - } -} diff --git a/MediaBrowser.Api/Images/ImageRequest.cs b/MediaBrowser.Api/Images/ImageRequest.cs new file mode 100644 index 0000000000..96e7a39cc4 --- /dev/null +++ b/MediaBrowser.Api/Images/ImageRequest.cs @@ -0,0 +1,54 @@ +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Api.Images +{ + /// + /// Class ImageRequest + /// + public class ImageRequest : DeleteImageRequest + { + /// + /// The max width + /// + public int? MaxWidth; + /// + /// The max height + /// + public int? MaxHeight; + /// + /// The width + /// + public int? Width; + /// + /// The height + /// + public int? Height; + /// + /// Gets or sets the quality. + /// + /// The quality. + public int? Quality { get; set; } + /// + /// Gets or sets the tag. + /// + /// The tag. + public string Tag { get; set; } + } + + /// + /// Class DeleteImageRequest + /// + public class DeleteImageRequest + { + /// + /// Gets or sets the type of the image. + /// + /// The type of the image. + public ImageType Type { get; set; } + /// + /// Gets or sets the index. + /// + /// The index. + public int? Index { get; set; } + } +} diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs new file mode 100644 index 0000000000..5e5888b3e5 --- /dev/null +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -0,0 +1,286 @@ +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; +using ServiceStack.ServiceHost; +using System; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.Images +{ + /// + /// Class GetItemImage + /// + [Route("/Items/{Id}/Images/{Type}", "GET")] + [Route("/Items/{Id}/Images/{Type}/{Index}", "GET")] + public class GetItemImage : ImageRequest + { + /// + /// Gets or sets the id. + /// + /// The id. + public string Id { get; set; } + } + + /// + /// Class GetPersonImage + /// + [Route("/Persons/{Name}/Images/{Type}", "GET")] + [Route("/Persons/{Name}/Images/{Type}/{Index}", "GET")] + public class GetPersonImage : ImageRequest + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + } + + /// + /// Class GetStudioImage + /// + [Route("/Studios/{Name}/Images/{Type}", "GET")] + [Route("/Studios/{Name}/Images/{Type}/{Index}", "GET")] + public class GetStudioImage : ImageRequest + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + } + + /// + /// Class GetGenreImage + /// + [Route("/Genres/{Name}/Images/{Type}", "GET")] + [Route("/Genres/{Name}/Images/{Type}/{Index}", "GET")] + public class GetGenreImage : ImageRequest + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + } + + /// + /// Class GetYearImage + /// + [Route("/Years/{Year}/Images/{Type}", "GET")] + [Route("/Years/{Year}/Images/{Type}/{Index}", "GET")] + public class GetYearImage : ImageRequest + { + /// + /// Gets or sets the year. + /// + /// The year. + public int Year { get; set; } + } + + /// + /// Class GetUserImage + /// + [Route("/Users/{Id}/Images/{Type}", "GET")] + [Route("/Users/{Id}/Images/{Type}/{Index}", "GET")] + public class GetUserImage : ImageRequest + { + /// + /// Gets or sets the id. + /// + /// The id. + public Guid Id { get; set; } + } + + /// + /// Class DeleteUserImage + /// + [Route("/Users/{Id}/Images/{Type}", "DELETE")] + [Route("/Users/{Id}/Images/{Type}/{Index}", "DELETE")] + public class DeleteUserImage : DeleteImageRequest + { + /// + /// Gets or sets the id. + /// + /// The id. + public Guid Id { get; set; } + } + + /// + /// Class ImageService + /// + [Export(typeof(IRestfulService))] + public class ImageService : BaseRestService + { + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetItemImage request) + { + var item = DtoBuilder.GetItemByClientId(request.Id); + + return GetImage(request, item); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetUserImage request) + { + var kernel = (Kernel)Kernel; + + var item = kernel.Users.First(i => i.Id == request.Id); + + return GetImage(request, item); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetYearImage request) + { + var kernel = (Kernel)Kernel; + + var item = kernel.LibraryManager.GetYear(request.Year).Result; + + return GetImage(request, item); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetStudioImage request) + { + var kernel = (Kernel)Kernel; + + var item = kernel.LibraryManager.GetStudio(request.Name).Result; + + return GetImage(request, item); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetPersonImage request) + { + var kernel = (Kernel)Kernel; + + var item = kernel.LibraryManager.GetPerson(request.Name).Result; + + return GetImage(request, item); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetGenreImage request) + { + var kernel = (Kernel)Kernel; + + var item = kernel.LibraryManager.GetGenre(request.Name).Result; + + return GetImage(request, item); + } + + /// + /// Deletes the specified request. + /// + /// The request. + public void Delete(DeleteUserImage request) + { + var kernel = (Kernel)Kernel; + + var item = kernel.Users.First(i => i.Id == request.Id); + + var task = item.DeleteImage(request.Type); + + Task.WaitAll(task); + } + + /// + /// Gets the image. + /// + /// The request. + /// The item. + /// System.Object. + /// + private object GetImage(ImageRequest request, BaseItem item) + { + var kernel = (Kernel)Kernel; + + var index = request.Index ?? 0; + + var imagePath = GetImagePath(kernel, request, item); + + if (string.IsNullOrEmpty(imagePath)) + { + throw new ResourceNotFoundException(); + } + + // See if we can avoid a file system lookup by looking for the file in ResolveArgs + var originalFileImageDateModified = kernel.ImageManager.GetImageDateModified(item, request.Type, index); + + var supportedImageEnhancers = kernel.ImageEnhancers.Where(i => i.Supports(item, request.Type)).ToList(); + + // If the file does not exist GetLastWriteTimeUtc will return jan 1, 1601 as opposed to throwing an exception + // http://msdn.microsoft.com/en-us/library/system.io.file.getlastwritetimeutc.aspx + if (originalFileImageDateModified.Year == 1601 && !File.Exists(imagePath)) + { + throw new ResourceNotFoundException(string.Format("File not found: {0}", imagePath)); + } + + var contentType = MimeTypes.GetMimeType(imagePath); + var dateLastModified = (supportedImageEnhancers.Select(e => e.LastConfigurationChange(item, request.Type)).Concat(new[] { originalFileImageDateModified })).Max(); + + var cacheGuid = kernel.ImageManager.GetImageCacheTag(imagePath, originalFileImageDateModified, supportedImageEnhancers, item, request.Type); + + TimeSpan? cacheDuration = null; + + if (!string.IsNullOrEmpty(request.Tag) && cacheGuid == new Guid(request.Tag)) + { + cacheDuration = TimeSpan.FromDays(365); + } + + return ToCachedResult(cacheGuid, dateLastModified, cacheDuration, () => new ImageWriter + { + Item = item, + Request = request, + CropWhiteSpace = request.Type == ImageType.Logo || request.Type == ImageType.Art, + OriginalImageDateModified = originalFileImageDateModified, + ContentType = contentType + + }, contentType); + } + + /// + /// Gets the image path. + /// + /// The kernel. + /// The request. + /// The item. + /// System.String. + private string GetImagePath(Kernel kernel, ImageRequest request, BaseItem item) + { + var index = request.Index ?? 0; + + return kernel.ImageManager.GetImagePath(item, request.Type, index); + } + } +} diff --git a/MediaBrowser.Api/Images/ImageWriter.cs b/MediaBrowser.Api/Images/ImageWriter.cs new file mode 100644 index 0000000000..22c8510568 --- /dev/null +++ b/MediaBrowser.Api/Images/ImageWriter.cs @@ -0,0 +1,80 @@ +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using ServiceStack.Service; +using ServiceStack.ServiceHost; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.Images +{ + /// + /// Class ImageWriter + /// + public class ImageWriter : IStreamWriter, IHasOptions + { + /// + /// Gets or sets the request. + /// + /// The request. + public ImageRequest Request { get; set; } + /// + /// Gets or sets the item. + /// + /// The item. + public BaseItem Item { get; set; } + /// + /// Gets or sets a value indicating whether [crop white space]. + /// + /// true if [crop white space]; otherwise, false. + public bool CropWhiteSpace { get; set; } + /// + /// The original image date modified + /// + public DateTime OriginalImageDateModified; + /// + /// Gets or sets the type of the content. + /// + /// The type of the content. + public string ContentType { get; set; } + + /// + /// Writes to. + /// + /// The response stream. + public void WriteTo(Stream responseStream) + { + Options["Content-Type"] = ContentType; + + var task = WriteToAsync(responseStream); + + Task.WaitAll(task); + } + + /// + /// Writes to async. + /// + /// The response stream. + /// Task. + private Task WriteToAsync(Stream responseStream) + { + return Kernel.Instance.ImageManager.ProcessImage(Item, Request.Type, Request.Index ?? 0, CropWhiteSpace, + OriginalImageDateModified, responseStream, Request.Width, Request.Height, Request.MaxWidth, + Request.MaxHeight, Request.Quality); + } + + /// + /// The _options + /// + private readonly IDictionary _options = new Dictionary { }; + /// + /// Gets the options. + /// + /// The options. + public IDictionary Options + { + get { return _options; } + } + } +} diff --git a/MediaBrowser.Api/Images/UploadImageHandler.cs b/MediaBrowser.Api/Images/UploadImageHandler.cs new file mode 100644 index 0000000000..13ea865631 --- /dev/null +++ b/MediaBrowser.Api/Images/UploadImageHandler.cs @@ -0,0 +1,143 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net.Handlers; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; +using System; +using System.ComponentModel.Composition; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.Images +{ + /// + /// Class UploadImageHandler + /// + [Export(typeof(IHttpServerHandler))] + class UploadImageHandler : BaseActionHandler + { + /// + /// The _source entity + /// + private BaseItem _sourceEntity; + + /// + /// Gets the source entity. + /// + /// Task{BaseItem}. + private async Task GetSourceEntity() + { + if (_sourceEntity == null) + { + if (!string.IsNullOrEmpty(QueryString["personname"])) + { + _sourceEntity = + await Kernel.LibraryManager.GetPerson(QueryString["personname"]).ConfigureAwait(false); + } + + else if (!string.IsNullOrEmpty(QueryString["genre"])) + { + _sourceEntity = + await Kernel.LibraryManager.GetGenre(QueryString["genre"]).ConfigureAwait(false); + } + + else if (!string.IsNullOrEmpty(QueryString["year"])) + { + _sourceEntity = + await + Kernel.LibraryManager.GetYear(int.Parse(QueryString["year"])).ConfigureAwait(false); + } + + else if (!string.IsNullOrEmpty(QueryString["studio"])) + { + _sourceEntity = + await Kernel.LibraryManager.GetStudio(QueryString["studio"]).ConfigureAwait(false); + } + + else if (!string.IsNullOrEmpty(QueryString["userid"])) + { + _sourceEntity = ApiService.GetUserById(QueryString["userid"]); + } + + else + { + _sourceEntity = DtoBuilder.GetItemByClientId(QueryString["id"]); + } + } + + return _sourceEntity; + } + + /// + /// Gets the type of the image. + /// + /// The type of the image. + private ImageType ImageType + { + get + { + var imageType = QueryString["type"]; + + return (ImageType)Enum.Parse(typeof(ImageType), imageType, true); + } + } + + /// + /// Performs the action. + /// + /// Task. + protected override async Task ExecuteAction() + { + var entity = await GetSourceEntity().ConfigureAwait(false); + + using (var reader = new StreamReader(HttpListenerContext.Request.InputStream)) + { + var text = await reader.ReadToEndAsync().ConfigureAwait(false); + + var bytes = Convert.FromBase64String(text); + + string filename; + + switch (ImageType) + { + case ImageType.Art: + filename = "clearart"; + break; + case ImageType.Primary: + filename = "folder"; + break; + default: + filename = ImageType.ToString().ToLower(); + break; + } + + // Use the client filename to determine the original extension + var clientFileName = QueryString["filename"]; + + var oldImagePath = entity.GetImage(ImageType); + + var imagePath = Path.Combine(entity.MetaLocation, filename + Path.GetExtension(clientFileName)); + + // Save to file system + using (var fs = new FileStream(imagePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, true)) + { + await fs.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + } + + // Set the image + entity.SetImage(ImageType, imagePath); + + // If the new and old paths are different, delete the old one + if (!string.IsNullOrEmpty(oldImagePath) && !oldImagePath.Equals(imagePath, StringComparison.OrdinalIgnoreCase)) + { + File.Delete(oldImagePath); + } + + // Directory watchers should repeat this, but do a quick refresh first + await entity.RefreshMetadata(CancellationToken.None, forceSave: true, allowSlowProviders: false).ConfigureAwait(false); + } + } + } +} diff --git a/MediaBrowser.Api/LibraryService.cs b/MediaBrowser.Api/LibraryService.cs new file mode 100644 index 0000000000..a8eb46105f --- /dev/null +++ b/MediaBrowser.Api/LibraryService.cs @@ -0,0 +1,250 @@ +using MediaBrowser.Common.Mef; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.DTO; +using MediaBrowser.Model.Entities; +using ServiceStack.ServiceHost; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; + +namespace MediaBrowser.Api +{ + /// + /// Class GetPhyscialPaths + /// + [Route("/Library/PhysicalPaths", "GET")] + public class GetPhyscialPaths : IReturn> + { + } + + /// + /// Class GetItemTypes + /// + [Route("/Library/ItemTypes", "GET")] + public class GetItemTypes : IReturn> + { + /// + /// Gets or sets a value indicating whether this instance has internet provider. + /// + /// true if this instance has internet provider; otherwise, false. + public bool HasInternetProvider { get; set; } + } + + /// + /// Class GetPerson + /// + [Route("/Library/Persons/{Name}", "GET")] + public class GetPerson : IReturn + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + } + + /// + /// Class GetStudio + /// + [Route("/Library/Studios/{Name}", "GET")] + public class GetStudio : IReturn + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + } + + /// + /// Class GetGenre + /// + [Route("/Library/Genres/{Name}", "GET")] + public class GetGenre : IReturn + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + } + + /// + /// Class GetYear + /// + [Route("/Library/Years/{Year}", "GET")] + public class GetYear : IReturn + { + /// + /// Gets or sets the year. + /// + /// The year. + public int Year { get; set; } + } + + /// + /// Class GetDefaultVirtualFolders + /// + [Route("/Library/DefaultVirtualFolders", "GET")] + public class GetDefaultVirtualFolders : IReturn> + { + } + + /// + /// Class LibraryService + /// + [Export(typeof(IRestfulService))] + public class LibraryService : BaseRestService + { + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetDefaultVirtualFolders request) + { + var kernel = (Kernel)Kernel; + + var result = kernel.LibraryManager.GetDefaultVirtualFolders().ToList(); + + return ToOptimizedResult(result); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetPerson request) + { + var kernel = (Kernel)Kernel; + + var item = kernel.LibraryManager.GetPerson(request.Name).Result; + + // Get everything + var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)); + + var result = DtoBuilder.GetDtoBaseItem(item, fields.ToList()).Result; + + return ToOptimizedResult(result); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetGenre request) + { + var kernel = (Kernel)Kernel; + + var item = kernel.LibraryManager.GetGenre(request.Name).Result; + + // Get everything + var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)); + + var result = DtoBuilder.GetDtoBaseItem(item, fields.ToList()).Result; + + return ToOptimizedResult(result); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetStudio request) + { + var kernel = (Kernel)Kernel; + + var item = kernel.LibraryManager.GetStudio(request.Name).Result; + + // Get everything + var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)); + + var result = DtoBuilder.GetDtoBaseItem(item, fields.ToList()).Result; + + return ToOptimizedResult(result); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetYear request) + { + var kernel = (Kernel)Kernel; + + var item = kernel.LibraryManager.GetYear(request.Year).Result; + + // Get everything + var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)); + + var result = DtoBuilder.GetDtoBaseItem(item, fields.ToList()).Result; + + return ToOptimizedResult(result); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetPhyscialPaths request) + { + var kernel = (Kernel)Kernel; + + var result = kernel.RootFolder.Children.SelectMany(c => c.ResolveArgs.PhysicalLocations).ToList(); + + return ToOptimizedResult(result); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetItemTypes request) + { + var kernel = (Kernel)Kernel; + + var allTypes = kernel.Assemblies.SelectMany(MefUtils.GetTypes).Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(BaseItem))); + + if (request.HasInternetProvider) + { + allTypes = allTypes.Where(t => + { + if (t == typeof(UserRootFolder) || t == typeof(AggregateFolder) || t == typeof(Folder) || t == typeof(IndexFolder) || t == typeof(CollectionFolder) || t == typeof(Year)) + { + return false; + } + + if (t == typeof(User)) + { + return false; + } + + // For now it seems internet providers generally only deal with video subclasses + if (t == typeof(Video)) + { + return false; + } + + if (t.IsSubclassOf(typeof(BasePluginFolder))) + { + return false; + } + + return true; + }); + } + + return allTypes.Select(t => t.Name).OrderBy(s => s).ToList(); + } + } +} diff --git a/MediaBrowser.Api/LocalizationService.cs b/MediaBrowser.Api/LocalizationService.cs new file mode 100644 index 0000000000..ce9f175e3d --- /dev/null +++ b/MediaBrowser.Api/LocalizationService.cs @@ -0,0 +1,112 @@ +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Localization; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MoreLinq; +using ServiceStack.ServiceHost; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Globalization; +using System.Linq; + +namespace MediaBrowser.Api +{ + /// + /// Class GetCultures + /// + [Route("/Localization/Cultures", "GET")] + public class GetCultures : IReturn> + { + } + + /// + /// Class GetCountries + /// + [Route("/Localization/Countries", "GET")] + public class GetCountries : IReturn> + { + } + + /// + /// Class ParentalRatings + /// + [Route("/Localization/ParentalRatings", "GET")] + public class GetParentalRatings : IReturn> + { + } + + /// + /// Class CulturesService + /// + [Export(typeof(IRestfulService))] + public class LocalizationService : BaseRestService + { + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetParentalRatings request) + { + var ratings = + Ratings.RatingsDict.Select(k => new ParentalRating { Name = k.Key, Value = k.Value }); + + var result = ratings.OrderBy(p => p.Value).Where(p => p.Value > 0).ToList(); + + return ToOptimizedResult(result); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetCountries request) + { + var result = CultureInfo.GetCultures(CultureTypes.SpecificCultures) + + .Select(c => new RegionInfo(c.LCID)) + .OrderBy(c => c.DisplayName) + + // Try to eliminate dupes + .DistinctBy(c => c.TwoLetterISORegionName) + + .Select(c => new CountryInfo + { + Name = c.Name, + DisplayName = c.DisplayName, + TwoLetterISORegionName = c.TwoLetterISORegionName, + ThreeLetterISORegionName = c.ThreeLetterISORegionName + }) + .ToList(); + + return ToOptimizedResult(result); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetCultures request) + { + var result = CultureInfo.GetCultures(CultureTypes.AllCultures) + .OrderBy(c => c.DisplayName) + + // Try to eliminate dupes + .DistinctBy(c => c.TwoLetterISOLanguageName + c.ThreeLetterISOLanguageName) + + .Select(c => new CultureDto + { + Name = c.Name, + DisplayName = c.DisplayName, + ThreeLetterISOLanguageName = c.ThreeLetterISOLanguageName, + TwoLetterISOLanguageName = c.TwoLetterISOLanguageName + }) + .ToList(); + + return ToOptimizedResult(result); + } + } + +} diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 1af7e71bd0..baa515fb20 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -1,117 +1,161 @@ - - - - - Debug - AnyCPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582} - Library - Properties - MediaBrowser.Api - MediaBrowser.Api - v4.5 - 512 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - Always - - - - - - - - False - ..\packages\Rx-Core.2.0.20823\lib\Net45\System.Reactive.Core.dll - - - False - ..\packages\Rx-Interfaces.2.0.20823\lib\Net45\System.Reactive.Interfaces.dll - - - False - ..\packages\Rx-Linq.2.0.20823\lib\Net45\System.Reactive.Linq.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {9142eefa-7570-41e1-bfcc-468bb571af2f} - MediaBrowser.Common - - - {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} - MediaBrowser.Controller - - - {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} - MediaBrowser.Model - - - - - - - - xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData-Server\Plugins\" /y - - + + + + + Debug + AnyCPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582} + Library + Properties + MediaBrowser.Api + MediaBrowser.Api + v4.5 + 512 + ..\ + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + AnyCPU + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + Always + + + + ..\packages\morelinq.1.0.15631-beta\lib\net35\MoreLinq.dll + + + False + ..\packages\protobuf-net.2.0.0.621\lib\net40\protobuf-net.dll + + + ..\packages\ServiceStack.3.9.37\lib\net35\ServiceStack.dll + + + ..\packages\ServiceStack.Common.3.9.37\lib\net35\ServiceStack.Common.dll + + + ..\packages\ServiceStack.Common.3.9.37\lib\net35\ServiceStack.Interfaces.dll + + + ..\packages\ServiceStack.OrmLite.SqlServer.3.9.37\lib\ServiceStack.OrmLite.dll + + + ..\packages\ServiceStack.OrmLite.SqlServer.3.9.37\lib\ServiceStack.OrmLite.SqlServer.dll + + + ..\packages\ServiceStack.Redis.3.9.37\lib\net35\ServiceStack.Redis.dll + + + ..\packages\ServiceStack.3.9.37\lib\net35\ServiceStack.ServiceInterface.dll + + + ..\packages\ServiceStack.Text.3.9.37\lib\net35\ServiceStack.Text.dll + + + + + + + + False + ..\packages\Rx-Core.2.0.21114\lib\Net45\System.Reactive.Core.dll + + + False + ..\packages\Rx-Interfaces.2.0.21114\lib\Net45\System.Reactive.Interfaces.dll + + + False + ..\packages\Rx-Linq.2.0.21114\lib\Net45\System.Reactive.Linq.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {9142eefa-7570-41e1-bfcc-468bb571af2f} + MediaBrowser.Common + + + {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} + MediaBrowser.Controller + + + {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} + MediaBrowser.Model + + + + + + + + + + + + xcopy "$(TargetPath)" "$(SolutionDir)\MediaBrowser.ServerApplication\" /y + + + \ No newline at end of file diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs new file mode 100644 index 0000000000..64c5d6cbc7 --- /dev/null +++ b/MediaBrowser.Api/PackageService.cs @@ -0,0 +1,201 @@ +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller; +using MediaBrowser.Model.Tasks; +using MediaBrowser.Model.Updates; +using ServiceStack.ServiceHost; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Api +{ + /// + /// Class GetPackage + /// + [Route("/Packages/{Name}", "GET")] + public class GetPackage : IReturn + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + } + + /// + /// Class GetPackages + /// + [Route("/Packages", "GET")] + public class GetPackages : IReturn> + { + /// + /// Gets or sets the name. + /// + /// The name. + public PackageType? PackageType { get; set; } + } + + /// + /// Class GetPackageVersionUpdates + /// + [Route("/Packages/Updates", "GET")] + public class GetPackageVersionUpdates : IReturn> + { + /// + /// Gets or sets the name. + /// + /// The name. + public PackageType PackageType { get; set; } + } + + /// + /// Class InstallPackage + /// + [Route("/Packages/Installed/{Name}", "POST")] + public class InstallPackage : IReturnVoid + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + + /// + /// Gets or sets the version. + /// + /// The version. + public string Version { get; set; } + + /// + /// Gets or sets the update class. + /// + /// The update class. + public PackageVersionClass UpdateClass { get; set; } + } + + /// + /// Class CancelPackageInstallation + /// + [Route("/Packages/Installing/{Id}", "DELETE")] + public class CancelPackageInstallation : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + public Guid Id { get; set; } + } + + /// + /// Class PackageService + /// + [Export(typeof(IRestfulService))] + public class PackageService : BaseRestService + { + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + /// Unsupported PackageType + public object Get(GetPackageVersionUpdates request) + { + var kernel = (Kernel)Kernel; + + var result = new List(); + + if (request.PackageType == PackageType.UserInstalled || request.PackageType == PackageType.All) + { + result.AddRange(kernel.InstallationManager.GetAvailablePluginUpdates(false, CancellationToken.None).Result.ToList()); + } + + else if (request.PackageType == PackageType.System || request.PackageType == PackageType.All) + { + var updateCheckResult = new ApplicationUpdateCheck().CheckForApplicationUpdate(CancellationToken.None, new Progress { }).Result; + + if (updateCheckResult.UpdateAvailable) + { + result.Add(new PackageVersionInfo + { + versionStr = updateCheckResult.AvailableVersion.ToString() + }); + } + } + + return ToOptimizedResult(result); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetPackage request) + { + var kernel = (Kernel)Kernel; + + var packages = kernel.InstallationManager.GetAvailablePackages(CancellationToken.None, applicationVersion: kernel.ApplicationVersion).Result; + + var result = packages.FirstOrDefault(p => p.name.Equals(request.Name, StringComparison.OrdinalIgnoreCase)); + + return ToOptimizedResult(result); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetPackages request) + { + var kernel = (Kernel)Kernel; + + var packages = kernel.InstallationManager.GetAvailablePackages(CancellationToken.None, request.PackageType, kernel.ApplicationVersion).Result; + + return ToOptimizedResult(packages.ToList()); + } + + /// + /// Posts the specified request. + /// + /// The request. + /// + public void Post(InstallPackage request) + { + var kernel = (Kernel)Kernel; + + var package = string.IsNullOrEmpty(request.Version) ? + kernel.InstallationManager.GetLatestCompatibleVersion(request.Name, request.UpdateClass).Result : + kernel.InstallationManager.GetPackage(request.Name, request.UpdateClass, Version.Parse(request.Version)).Result; + + if (package == null) + { + throw new ResourceNotFoundException(string.Format("Package not found: {0}", request.Name)); + } + + Task.Run(() => kernel.InstallationManager.InstallPackage(package, new Progress { }, CancellationToken.None)); + } + + /// + /// Deletes the specified request. + /// + /// The request. + public void Delete(CancelPackageInstallation request) + { + var kernel = (Kernel)Kernel; + + var info = kernel.InstallationManager.CurrentInstallations.FirstOrDefault(i => i.Item1.Id == request.Id); + + if (info != null) + { + info.Item2.Cancel(); + } + } + } + +} diff --git a/MediaBrowser.Api/Plugin.cs b/MediaBrowser.Api/Plugin.cs index 8def96da8d..c9e1d7c953 100644 --- a/MediaBrowser.Api/Plugin.cs +++ b/MediaBrowser.Api/Plugin.cs @@ -1,14 +1,329 @@ -using MediaBrowser.Common.Plugins; -using System.ComponentModel.Composition; - -namespace MediaBrowser.Api -{ - [Export(typeof(BasePlugin))] - public class Plugin : BasePlugin - { - public override string Name - { - get { return "Media Browser API"; } - } - } -} +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Plugins; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.Composition; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Api +{ + /// + /// Class Plugin + /// + [Export(typeof(IPlugin))] + public class Plugin : BasePlugin + { + /// + /// Gets the name of the plugin + /// + /// The name. + public override string Name + { + get { return "Web Api"; } + } + + /// + /// Gets a value indicating whether this instance is a core plugin. + /// + /// true if this instance is a core plugin; otherwise, false. + public override bool IsCorePlugin + { + get + { + return true; + } + } + + /// + /// Gets the instance. + /// + /// The instance. + public static Plugin Instance { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + public Plugin() + { + Instance = this; + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected override void DisposeOnServer(bool dispose) + { + if (dispose) + { + var jobCount = ActiveTranscodingJobs.Count; + + Parallel.ForEach(ActiveTranscodingJobs, OnTranscodeKillTimerStopped); + + // Try to allow for some time to kill the ffmpeg processes and delete the partial stream files + if (jobCount > 0) + { + Thread.Sleep(1000); + } + } + + base.DisposeOnServer(dispose); + } + + /// + /// The active transcoding jobs + /// + private readonly List ActiveTranscodingJobs = new List(); + + /// + /// Called when [transcode beginning]. + /// + /// The path. + /// The type. + /// The process. + public void OnTranscodeBeginning(string path, TranscodingJobType type, Process process) + { + lock (ActiveTranscodingJobs) + { + ActiveTranscodingJobs.Add(new TranscodingJob + { + Type = type, + Path = path, + Process = process, + ActiveRequestCount = 1 + }); + } + } + + /// + /// Called when [transcode failed to start]. + /// + /// The path. + /// The type. + public void OnTranscodeFailedToStart(string path, TranscodingJobType type) + { + lock (ActiveTranscodingJobs) + { + var job = ActiveTranscodingJobs.First(j => j.Type == type && j.Path.Equals(path, StringComparison.OrdinalIgnoreCase)); + + ActiveTranscodingJobs.Remove(job); + } + } + + /// + /// Determines whether [has active transcoding job] [the specified path]. + /// + /// The path. + /// The type. + /// true if [has active transcoding job] [the specified path]; otherwise, false. + public bool HasActiveTranscodingJob(string path, TranscodingJobType type) + { + lock (ActiveTranscodingJobs) + { + return ActiveTranscodingJobs.Any(j => j.Type == type && j.Path.Equals(path, StringComparison.OrdinalIgnoreCase)); + } + } + + /// + /// Called when [transcode begin request]. + /// + /// The path. + /// The type. + public void OnTranscodeBeginRequest(string path, TranscodingJobType type) + { + lock (ActiveTranscodingJobs) + { + var job = ActiveTranscodingJobs.FirstOrDefault(j => j.Type == type && j.Path.Equals(path, StringComparison.OrdinalIgnoreCase)); + + if (job == null) + { + return; + } + + job.ActiveRequestCount++; + + if (job.KillTimer != null) + { + job.KillTimer.Dispose(); + job.KillTimer = null; + } + } + } + + /// + /// Called when [transcode end request]. + /// + /// The path. + /// The type. + public void OnTranscodeEndRequest(string path, TranscodingJobType type) + { + lock (ActiveTranscodingJobs) + { + var job = ActiveTranscodingJobs.FirstOrDefault(j => j.Type == type && j.Path.Equals(path, StringComparison.OrdinalIgnoreCase)); + + if (job == null) + { + return; + } + + job.ActiveRequestCount--; + + if (job.ActiveRequestCount == 0) + { + var timerDuration = type == TranscodingJobType.Progressive ? 1000 : 30000; + + if (job.KillTimer == null) + { + job.KillTimer = new Timer(OnTranscodeKillTimerStopped, job, timerDuration, Timeout.Infinite); + } + else + { + job.KillTimer.Change(timerDuration, Timeout.Infinite); + } + } + } + } + + /// + /// Called when [transcoding finished]. + /// + /// The path. + /// The type. + public void OnTranscodingFinished(string path, TranscodingJobType type) + { + lock (ActiveTranscodingJobs) + { + var job = ActiveTranscodingJobs.FirstOrDefault(j => j.Type == type && j.Path.Equals(path, StringComparison.OrdinalIgnoreCase)); + + if (job == null) + { + return; + } + + ActiveTranscodingJobs.Remove(job); + + if (job.KillTimer != null) + { + job.KillTimer.Dispose(); + job.KillTimer = null; + } + } + } + + /// + /// Called when [transcode kill timer stopped]. + /// + /// The state. + private void OnTranscodeKillTimerStopped(object state) + { + var job = (TranscodingJob)state; + + lock (ActiveTranscodingJobs) + { + ActiveTranscodingJobs.Remove(job); + + if (job.KillTimer != null) + { + job.KillTimer.Dispose(); + job.KillTimer = null; + } + } + + var process = job.Process; + + var hasExited = true; + + try + { + hasExited = process.HasExited; + } + catch (Win32Exception ex) + { + Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path); + } + catch (InvalidOperationException ex) + { + Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path); + } + catch (NotSupportedException ex) + { + Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path); + } + + if (hasExited) + { + return; + } + + try + { + Logger.Info("Killing ffmpeg process for {0}", job.Path); + + process.Kill(); + } + catch (Win32Exception ex) + { + Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); + } + catch (InvalidOperationException ex) + { + Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); + } + catch (NotSupportedException ex) + { + Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); + } + } + } + + /// + /// Class TranscodingJob + /// + public class TranscodingJob + { + /// + /// Gets or sets the path. + /// + /// The path. + public string Path { get; set; } + /// + /// Gets or sets the type. + /// + /// The type. + public TranscodingJobType Type { get; set; } + /// + /// Gets or sets the process. + /// + /// The process. + public Process Process { get; set; } + /// + /// Gets or sets the active request count. + /// + /// The active request count. + public int ActiveRequestCount { get; set; } + /// + /// Gets or sets the kill timer. + /// + /// The kill timer. + public Timer KillTimer { get; set; } + } + + /// + /// Enum TranscodingJobType + /// + public enum TranscodingJobType + { + /// + /// The progressive + /// + Progressive, + /// + /// The HLS + /// + Hls + } +} diff --git a/MediaBrowser.Api/PluginService.cs b/MediaBrowser.Api/PluginService.cs new file mode 100644 index 0000000000..ad7e37adc2 --- /dev/null +++ b/MediaBrowser.Api/PluginService.cs @@ -0,0 +1,241 @@ +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Common.Serialization; +using MediaBrowser.Controller; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Plugins; +using ServiceStack.ServiceHost; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using ServiceStack.Text.Controller; + +namespace MediaBrowser.Api +{ + /// + /// Class Plugins + /// + [Route("/Plugins", "GET")] + public class GetPlugins : IReturn> + { + } + + /// + /// Class GetPluginAssembly + /// + [Route("/Plugins/{Id}/Assembly", "GET")] + public class GetPluginAssembly + { + /// + /// Gets or sets the id. + /// + /// The id. + public Guid Id { get; set; } + } + + /// + /// Class UninstallPlugin + /// + [Route("/Plugins/{Id}", "DELETE")] + public class UninstallPlugin : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + public Guid Id { get; set; } + } + + /// + /// Class GetPluginConfiguration + /// + [Route("/Plugins/{Id}/Configuration", "GET")] + public class GetPluginConfiguration + { + /// + /// Gets or sets the id. + /// + /// The id. + public Guid Id { get; set; } + } + + /// + /// Class UpdatePluginConfiguration + /// + [Route("/Plugins/{Id}/Configuration", "POST")] + public class UpdatePluginConfiguration : IRequiresRequestStream, IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + public Guid Id { get; set; } + + /// + /// The raw Http Request Input Stream + /// + /// The request stream. + public Stream RequestStream { get; set; } + } + + /// + /// Class GetPluginConfigurationFile + /// + [Route("/Plugins/{Id}/ConfigurationFile", "GET")] + public class GetPluginConfigurationFile + { + /// + /// Gets or sets the id. + /// + /// The id. + public Guid Id { get; set; } + } + + /// + /// Class GetPluginSecurityInfo + /// + [Route("/Plugins/SecurityInfo", "GET")] + [Restrict(VisibleLocalhostOnly = true)] + public class GetPluginSecurityInfo : IReturn + { + } + + /// + /// Class UpdatePluginSecurityInfo + /// + [Route("/Plugins/SecurityInfo", "GET")] + public class UpdatePluginSecurityInfo : IReturnVoid, IRequiresRequestStream + { + /// + /// The raw Http Request Input Stream + /// + /// The request stream. + public Stream RequestStream { get; set; } + } + + /// + /// Class PluginsService + /// + [Export(typeof(IRestfulService))] + public class PluginService : BaseRestService + { + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetPlugins request) + { + var result = Kernel.Plugins.OrderBy(p => p.Name).Select(p => p.GetPluginInfo()).ToList(); + + return ToOptimizedResult(result); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetPluginAssembly request) + { + var plugin = Kernel.Plugins.First(p => p.UniqueId == request.Id); + + return ToStaticFileResult(plugin.AssemblyFilePath); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetPluginConfiguration request) + { + var plugin = Kernel.Plugins.First(p => p.UniqueId == request.Id); + + var dateModified = plugin.ConfigurationDateLastModified; + + var cacheKey = (plugin.Version.ToString() + dateModified.Ticks).GetMD5(); + + return ToOptimizedResultUsingCache(cacheKey, dateModified, null, () => plugin.Configuration); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetPluginConfigurationFile request) + { + var plugin = Kernel.Plugins.First(p => p.UniqueId == request.Id); + + return ToStaticFileResult(plugin.ConfigurationFilePath); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetPluginSecurityInfo request) + { + var kernel = (Kernel)Kernel; + + var result = new PluginSecurityInfo + { + IsMBSupporter = kernel.PluginSecurityManager.IsMBSupporter, + SupporterKey = kernel.PluginSecurityManager.SupporterKey, + LegacyKey = kernel.PluginSecurityManager.LegacyKey + }; + + return ToOptimizedResult(result); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(UpdatePluginSecurityInfo request) + { + var kernel = (Kernel)Kernel; + + var info = JsonSerializer.DeserializeFromStream(request.RequestStream); + + kernel.PluginSecurityManager.SupporterKey = info.SupporterKey; + kernel.PluginSecurityManager.LegacyKey = info.LegacyKey; + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(UpdatePluginConfiguration request) + { + // We need to parse this manually because we told service stack not to with IRequiresRequestStream + // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs + var pathInfo = PathInfo.Parse(Request.PathInfo); + var id = new Guid(pathInfo.GetArgumentValue(1)); + + var plugin = Kernel.Plugins.First(p => p.UniqueId == id); + + var configuration = JsonSerializer.DeserializeFromStream(request.RequestStream, plugin.ConfigurationType) as BasePluginConfiguration; + + plugin.UpdateConfiguration(configuration); + } + + /// + /// Deletes the specified request. + /// + /// The request. + public void Delete(UninstallPlugin request) + { + var kernel = (Kernel)Kernel; + + var plugin = kernel.Plugins.First(p => p.UniqueId == request.Id); + + kernel.InstallationManager.UninstallPlugin(plugin); + } + } +} diff --git a/MediaBrowser.Api/Properties/AssemblyInfo.cs b/MediaBrowser.Api/Properties/AssemblyInfo.cs index c92346bac1..ad5fb78d90 100644 --- a/MediaBrowser.Api/Properties/AssemblyInfo.cs +++ b/MediaBrowser.Api/Properties/AssemblyInfo.cs @@ -1,35 +1,34 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("MediaBrowser.Api")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("MediaBrowser.Api")] -[assembly: AssemblyCopyright("Copyright © 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("13464b02-f033-48b8-9e1c-d071f8860935")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MediaBrowser.Api")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MediaBrowser.Api")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("13464b02-f033-48b8-9e1c-d071f8860935")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.9.*")] diff --git a/MediaBrowser.Api/Streaming/AudioHandler.cs b/MediaBrowser.Api/Streaming/AudioHandler.cs new file mode 100644 index 0000000000..685e17dbc4 --- /dev/null +++ b/MediaBrowser.Api/Streaming/AudioHandler.cs @@ -0,0 +1,112 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net.Handlers; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Model.DTO; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Net; + +namespace MediaBrowser.Api.Streaming +{ + /// + /// Providers a progressive streaming audio api + /// + [Export(typeof(IHttpServerHandler))] + public class AudioHandler : BaseProgressiveStreamingHandler