├── .gitignore ├── ATEM Switcher Library.sln ├── MediaPool ├── App.config ├── MediaPool.cs ├── MediaPool.csproj ├── Properties │ └── AssemblyInfo.cs └── packages.config ├── MediaUpload ├── App.config ├── MediaUpload.cs ├── MediaUpload.csproj └── Properties │ └── AssemblyInfo.cs ├── Prep Release.cmd ├── SwitcherLib ├── Callbacks │ ├── Stills.cs │ └── UploadLock.cs ├── ConsoleUtils.cs ├── Log.cs ├── MediaStill.cs ├── Properties │ └── AssemblyInfo.cs ├── Switcher.cs ├── SwitcherLib.csproj ├── SwitcherLibException.cs └── Upload.cs └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | /Release 2 | /SwitcherLib/bin 3 | /MediaPool/bin 4 | /MediaUpload/bin 5 | /SwitcherLib/obj 6 | /MediaPool/obj 7 | /MediaUpload/obj 8 | /packages 9 | /*.suo 10 | -------------------------------------------------------------------------------- /ATEM Switcher Library.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwitcherLib", "SwitcherLib\SwitcherLib.csproj", "{5F9F52A6-9EF4-4C71-9A91-F5B864A57ABB}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaPool", "MediaPool\MediaPool.csproj", "{A17A1F1D-C1F7-4489-8D55-E75C5FEFFF69}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaUpload", "MediaUpload\MediaUpload.csproj", "{ED13F395-B644-42DC-9BBA-5157B238F277}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {5F9F52A6-9EF4-4C71-9A91-F5B864A57ABB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {5F9F52A6-9EF4-4C71-9A91-F5B864A57ABB}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {5F9F52A6-9EF4-4C71-9A91-F5B864A57ABB}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {5F9F52A6-9EF4-4C71-9A91-F5B864A57ABB}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {A17A1F1D-C1F7-4489-8D55-E75C5FEFFF69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {A17A1F1D-C1F7-4489-8D55-E75C5FEFFF69}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {A17A1F1D-C1F7-4489-8D55-E75C5FEFFF69}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {A17A1F1D-C1F7-4489-8D55-E75C5FEFFF69}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {ED13F395-B644-42DC-9BBA-5157B238F277}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {ED13F395-B644-42DC-9BBA-5157B238F277}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {ED13F395-B644-42DC-9BBA-5157B238F277}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {ED13F395-B644-42DC-9BBA-5157B238F277}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /MediaPool/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /MediaPool/MediaPool.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using SwitcherLib; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Xml.Serialization; 10 | 11 | namespace MediaPool 12 | { 13 | class MediaPool 14 | { 15 | private enum Format 16 | { 17 | Text, 18 | JSON, 19 | XML, 20 | CSV, 21 | } 22 | 23 | static int Main(string[] args) 24 | { 25 | try 26 | { 27 | MediaPool.ProcessArgs(args); 28 | return 0; 29 | } 30 | catch (SwitcherLibException ex) 31 | { 32 | Console.Error.WriteLine(ex.Message); 33 | return -1; 34 | } 35 | } 36 | 37 | private static void Help() 38 | { 39 | ConsoleUtils.Version(); 40 | Console.Out.WriteLine(); 41 | Console.Out.WriteLine("Usage: mediapool.exe [options] "); 42 | Console.Out.WriteLine("Gets the info for all the media in the media pool for an ATEM switcher"); 43 | Console.Out.WriteLine(); 44 | Console.Out.WriteLine("Arguments:"); 45 | Console.Out.WriteLine(); 46 | Console.Out.WriteLine(" hostname - The hostname or IP of the ATEM switcher"); 47 | Console.Out.WriteLine(); 48 | Console.Out.WriteLine("Options:"); 49 | Console.Out.WriteLine(); 50 | Console.Out.WriteLine(" -h, --help - This help message"); 51 | Console.Out.WriteLine(" -d, --debug - Debug output"); 52 | Console.Out.WriteLine(" -v, --version - Version information"); 53 | Console.Out.WriteLine(" -f, --format - The output format. Either xml, csv, json or text"); 54 | Console.Out.WriteLine(); 55 | } 56 | 57 | private static void ProcessArgs(string[] args) 58 | { 59 | IList args1 = new List(); 60 | MediaPool.Format format = MediaPool.Format.Text; 61 | for (int index = 0; index < args.Length; index++) 62 | { 63 | switch (args[index]) 64 | { 65 | case "-h": 66 | case "--help": 67 | case "-?": 68 | case "/?": 69 | case "/h": 70 | case "/help": 71 | MediaPool.Help(); 72 | return; 73 | 74 | case "-v": 75 | case "--version": 76 | case "/v": 77 | case "/version": 78 | ConsoleUtils.Version(); 79 | return; 80 | 81 | case "-d": 82 | case "--debug": 83 | case "/d": 84 | case "/debug": 85 | Log.CurrentLevel = Log.Level.Debug; 86 | break; 87 | 88 | case "-f": 89 | case "--format": 90 | if (args.Length > index) 91 | { 92 | switch (args[index + 1].ToLower()) 93 | { 94 | case "json": 95 | format = MediaPool.Format.JSON; 96 | break; 97 | case "xml": 98 | format = MediaPool.Format.XML; 99 | break; 100 | case "csv": 101 | format = MediaPool.Format.CSV; 102 | break; 103 | case "text": 104 | format = MediaPool.Format.Text; 105 | break; 106 | default: 107 | throw new SwitcherLibException(String.Format("Unknown format: {0}", format)); 108 | } 109 | index++; 110 | break; 111 | } 112 | break; 113 | 114 | default: 115 | args1.Add(args[index]); 116 | break; 117 | } 118 | } 119 | MediaPool.ListMediaPool(format, args1); 120 | } 121 | 122 | private static void ListMediaPool(MediaPool.Format format, IList args) 123 | { 124 | if (args.Count < 1) 125 | { 126 | MediaPool.Help(); 127 | throw new SwitcherLibException("Invalid arguments"); 128 | } 129 | 130 | Switcher switcher = new Switcher(args[0]); 131 | Log.Debug(String.Format("Switcher: {0}", switcher.GetProductName())); 132 | IList stills = switcher.GetStills(); 133 | 134 | switch (format) 135 | { 136 | case MediaPool.Format.Text: 137 | foreach (MediaStill still in stills) 138 | { 139 | Console.Out.WriteLine(); 140 | Console.Out.WriteLine(String.Format(" Name: {0}", still.Name)); 141 | Console.Out.WriteLine(String.Format(" Hash: {0}", still.Hash)); 142 | Console.Out.WriteLine(String.Format(" Slot: {0}", still.Slot.ToString())); 143 | Console.Out.WriteLine(String.Format(" Media Player: {0}", still.MediaPlayer.ToString())); 144 | } 145 | break; 146 | 147 | case MediaPool.Format.JSON: 148 | Console.Out.WriteLine(JsonConvert.SerializeObject(stills)); 149 | break; 150 | 151 | case MediaPool.Format.XML: 152 | XmlSerializer xml = new XmlSerializer(stills.GetType()); 153 | xml.Serialize(Console.Out, stills); 154 | break; 155 | 156 | case MediaPool.Format.CSV: 157 | foreach (MediaStill still in stills) 158 | { 159 | Console.Out.WriteLine(still.ToCSV()); 160 | } 161 | break; 162 | 163 | default: 164 | Console.Out.WriteLine(stills.ToString()); 165 | break; 166 | } 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /MediaPool/MediaPool.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {A17A1F1D-C1F7-4489-8D55-E75C5FEFFF69} 8 | Exe 9 | Properties 10 | MediaPool 11 | MediaPool 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | False 37 | packages\Newtonsoft.Json.6.0.7\lib\net45\Newtonsoft.Json.dll 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | {78a34361-5d3c-40aa-a98f-82311acaff49} 58 | SwitcherLib 59 | 60 | 61 | 62 | 69 | -------------------------------------------------------------------------------- /MediaPool/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ATEM Media Pool List")] 9 | [assembly: AssemblyDescription("Retrieves a list of media in the media pool of an ATEM Switcher")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Jessica Smith")] 12 | [assembly: AssemblyProduct("ATEM Switcher Library")] 13 | [assembly: AssemblyCopyright("© Copyright Jessica Smith, 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("d11131f6-2beb-4525-a08e-5958812bb536")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("2.0.1.0")] 36 | [assembly: AssemblyFileVersion("2.0.1.0")] 37 | -------------------------------------------------------------------------------- /MediaPool/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /MediaUpload/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /MediaUpload/MediaUpload.cs: -------------------------------------------------------------------------------- 1 | using SwitcherLib; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace MediaUpload 11 | { 12 | class MediaUpload 13 | { 14 | private static int Main(string[] args) 15 | { 16 | try 17 | { 18 | MediaUpload.ProcessArgs(args); 19 | return 0; 20 | } 21 | catch (SwitcherLibException ex) 22 | { 23 | Console.Error.WriteLine(ex.Message); 24 | return -1; 25 | } 26 | } 27 | 28 | private static void Help() 29 | { 30 | ConsoleUtils.Version(); 31 | Console.Out.WriteLine(); 32 | Console.Out.WriteLine("Usage: mediaupload.exe [options] "); 33 | Console.Out.WriteLine("Uploads an image to a BlackMagic ATEM switcher"); 34 | Console.Out.WriteLine(); 35 | Console.Out.WriteLine("Arguments:"); 36 | Console.Out.WriteLine(); 37 | Console.Out.WriteLine(" hostname - The hostname or IP of the ATEM switcher"); 38 | Console.Out.WriteLine(" slot - The number of the media slot to upload to"); 39 | Console.Out.WriteLine(" filename - The filename of the image to upload"); 40 | Console.Out.WriteLine(); 41 | Console.Out.WriteLine("Options:"); 42 | Console.Out.WriteLine(); 43 | Console.Out.WriteLine(" -h, --help - This help message"); 44 | Console.Out.WriteLine(" -d, --debug - Debug output"); 45 | Console.Out.WriteLine(" -v, --version - Version information"); 46 | Console.Out.WriteLine(" -n, --name - The name for the item in the media pool"); 47 | Console.Out.WriteLine(); 48 | Console.Out.WriteLine("Image Format:"); 49 | Console.Out.WriteLine(); 50 | Console.Out.WriteLine("The image must be the same resolution as the switcher. Accepted formats are BMP, JPEG, GIF, PNG and TIFF. Alpha channels are supported."); 51 | } 52 | 53 | private static void ProcessArgs(string[] args) 54 | { 55 | IList args1 = new List(); 56 | string name = ""; 57 | for (int index = 0; index < args.Length; index++) 58 | { 59 | switch (args[index]) 60 | { 61 | case "-h": 62 | case "--help": 63 | case "-?": 64 | case "/?": 65 | case "/h": 66 | case "/help": 67 | MediaUpload.Help(); 68 | return; 69 | 70 | case "-v": 71 | case "--version": 72 | case "/v": 73 | case "/version": 74 | ConsoleUtils.Version(); 75 | return; 76 | 77 | case "-d": 78 | case "--debug": 79 | case "/d": 80 | case "/debug": 81 | Log.CurrentLevel = Log.Level.Debug; 82 | break; 83 | 84 | case "-n": 85 | case "--name": 86 | case "/n": 87 | case "/name": 88 | if (index < args.Length) 89 | { 90 | name = args[index + 1]; 91 | index++; 92 | break; 93 | } 94 | break; 95 | 96 | default: 97 | args1.Add(args[index]); 98 | break; 99 | } 100 | } 101 | MediaUpload.Upload(name, args1); 102 | } 103 | 104 | private static void Upload(string name, IList args) 105 | { 106 | if (args.Count < 3) 107 | { 108 | MediaUpload.Help(); 109 | throw new SwitcherLibException("Invalid arguments"); 110 | } 111 | 112 | Switcher switcher = new Switcher(args[0]); 113 | int slot = MediaUpload.GetSlot(args[1]); 114 | Log.Debug(String.Format("Switcher: {0}", switcher.GetProductName())); 115 | Log.Debug(String.Format("Resolution: {0}x{1}", switcher.GetVideoWidth().ToString(), switcher.GetVideoHeight().ToString())); 116 | args.RemoveAt(0); 117 | args.RemoveAt(0); 118 | 119 | string filename = String.Join(" ", args); 120 | Upload upload = new Upload(switcher, filename, slot); 121 | if (name != "") 122 | { 123 | upload.SetName(name); 124 | } 125 | upload.Start(); 126 | while (upload.InProgress()) 127 | { 128 | Log.Info(String.Format("Progress: {0}%", upload.GetProgress().ToString())); 129 | Thread.Sleep(100); 130 | } 131 | } 132 | 133 | private static int GetSlot(string arg) 134 | { 135 | try 136 | { 137 | return Convert.ToInt32(arg) - 1; 138 | } 139 | catch (Exception ex) 140 | { 141 | throw new SwitcherLibException(String.Format("Invalid slot: {0}", arg), ex); 142 | } 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /MediaUpload/MediaUpload.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {ED13F395-B644-42DC-9BBA-5157B238F277} 8 | Exe 9 | Properties 10 | MediaUpload 11 | MediaUpload 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | {78a34361-5d3c-40aa-a98f-82311acaff49} 53 | SwitcherLib 54 | 55 | 56 | 57 | 64 | -------------------------------------------------------------------------------- /MediaUpload/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ATEM Media Upload")] 9 | [assembly: AssemblyDescription("Uploads still images to the media library of ATEM switchers")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Jessica Smith")] 12 | [assembly: AssemblyProduct("ATEM Switcher Library")] 13 | [assembly: AssemblyCopyright("© Copyright Jessica Smith, 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("196a68c6-a112-4239-9756-c44a7449c8cf")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("2.0.1.0")] 36 | [assembly: AssemblyFileVersion("2.0.1.0")] 37 | -------------------------------------------------------------------------------- /Prep Release.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | del /F /S /Q Release 3 | xcopy MediaPool\bin\release\MediaPool.exe Release /C /H /Y 4 | xcopy MediaPool\bin\release\*.dll Release /C /H /Y 5 | xcopy MediaUpload\bin\release\MediaUpload.exe Release /C /H /Y 6 | xcopy MediaUpload\bin\release\*.dll Release /C /H /Y 7 | xcopy readme.md Release\readme.md /C /H /Y 8 | pause -------------------------------------------------------------------------------- /SwitcherLib/Callbacks/Stills.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using BMDSwitcherAPI; 7 | 8 | namespace SwitcherLib.Callbacks 9 | { 10 | class Stills : IBMDSwitcherStillsCallback 11 | { 12 | private Upload upload; 13 | 14 | public Stills(Upload upload) 15 | { 16 | this.upload = upload; 17 | } 18 | 19 | public void Notify(_BMDSwitcherMediaPoolEventType eventType, IBMDSwitcherFrame frame, int index) 20 | { 21 | Log.Debug(String.Format("Stills Callback: {0}", eventType.ToString())); 22 | _BMDSwitcherMediaPoolEventType mediaPoolEventType = eventType; 23 | 24 | if (mediaPoolEventType != _BMDSwitcherMediaPoolEventType.bmdSwitcherMediaPoolEventTypeTransferCompleted) 25 | { 26 | return; 27 | } 28 | this.upload.TransferCompleted(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SwitcherLib/Callbacks/UploadLock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using BMDSwitcherAPI; 7 | 8 | namespace SwitcherLib.Callbacks 9 | { 10 | class UploadLock : IBMDSwitcherLockCallback 11 | { 12 | private Upload upload; 13 | 14 | public UploadLock(Upload upload) 15 | { 16 | this.upload = upload; 17 | } 18 | 19 | public void Obtained() 20 | { 21 | Log.Debug("Still upload lock obtained"); 22 | this.upload.LockCallback(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /SwitcherLib/ConsoleUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SwitcherLib 9 | { 10 | public class ConsoleUtils 11 | { 12 | public static void Version() 13 | { 14 | Version version = Assembly.GetEntryAssembly().GetName().Version; 15 | AssemblyTitleAttribute title = (AssemblyTitleAttribute)Assembly.GetEntryAssembly().GetCustomAttribute(typeof(AssemblyTitleAttribute)); 16 | Console.Out.WriteLine(String.Format("{0} {1}.{2}.{3}", title.Title, version.Major.ToString(), version.Minor.ToString(), version.Revision.ToString())); 17 | Console.Out.WriteLine("Jessica Smith "); 18 | Console.Out.WriteLine("This software is released under the MIT License"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwitcherLib/Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SwitcherLib 8 | { 9 | public class Log 10 | { 11 | public enum Level 12 | { 13 | Debug, 14 | Info, 15 | Notice, 16 | Warning, 17 | Error, 18 | } 19 | 20 | public static Log.Level CurrentLevel = Log.Level.Info; 21 | 22 | public static void Debug(string message) 23 | { 24 | Log.LogMessage(Log.Level.Debug, message); 25 | } 26 | 27 | public static void Info(string message) 28 | { 29 | Log.LogMessage(Log.Level.Info, message); 30 | } 31 | 32 | public static void Notice(string message) 33 | { 34 | Log.LogMessage(Log.Level.Notice, message); 35 | } 36 | 37 | public static void Warning(string message) 38 | { 39 | Log.LogMessage(Log.Level.Warning, message); 40 | } 41 | 42 | public static void Error(string message) 43 | { 44 | Log.LogMessage(Log.Level.Error, message); 45 | } 46 | 47 | protected static void LogMessage(Log.Level level, string message) 48 | { 49 | if (level < Log.CurrentLevel) 50 | { 51 | return; 52 | } 53 | Console.Out.WriteLine(message); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /SwitcherLib/MediaStill.cs: -------------------------------------------------------------------------------- 1 | using BMDSwitcherAPI; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SwitcherLib 9 | { 10 | public class MediaStill 11 | { 12 | public String Name; 13 | public String Hash; 14 | public int Slot; 15 | public int MediaPlayer; 16 | 17 | public MediaStill() 18 | { 19 | } 20 | 21 | public MediaStill(IBMDSwitcherStills stills, uint index) 22 | { 23 | BMDSwitcherHash hash; 24 | stills.GetHash(index, out hash); 25 | this.Hash = String.Join("", BitConverter.ToString(hash.data).Split('-')); 26 | stills.GetName(index, out this.Name); 27 | this.Slot = (int)index + 1; 28 | } 29 | 30 | public String ToCSV() 31 | { 32 | return String.Join(",", this.Slot.ToString(), "\"" + this.Name + "\"", "\"" + this.Hash + "\"", this.MediaPlayer.ToString()); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /SwitcherLib/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ATEM Switcher Library")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ATEM Switcher Library")] 13 | [assembly: AssemblyCopyright("© Copyright Jessica Smith, 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("b8ea4e08-548d-49a9-8fa0-50996b2d0362")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("2.0.1.0")] 36 | [assembly: AssemblyFileVersion("2.0.1.0")] 37 | -------------------------------------------------------------------------------- /SwitcherLib/Switcher.cs: -------------------------------------------------------------------------------- 1 | using BMDSwitcherAPI; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SwitcherLib 10 | { 11 | public class Switcher 12 | { 13 | protected IBMDSwitcher switcher; 14 | protected String deviceAddress; 15 | protected bool connected; 16 | 17 | public Switcher(string deviceAddress) 18 | { 19 | this.deviceAddress = deviceAddress; 20 | } 21 | 22 | public IBMDSwitcher GetSwitcher() 23 | { 24 | return this.switcher; 25 | } 26 | 27 | public void Connect() 28 | { 29 | if (this.connected) 30 | { 31 | return; 32 | } 33 | 34 | IBMDSwitcherDiscovery switcherDiscovery = new CBMDSwitcherDiscovery(); 35 | _BMDSwitcherConnectToFailure failReason = 0; 36 | 37 | try 38 | { 39 | switcherDiscovery.ConnectTo(this.deviceAddress, out this.switcher, out failReason); 40 | this.connected = true; 41 | } 42 | catch (COMException ex) 43 | { 44 | switch (failReason) 45 | { 46 | case _BMDSwitcherConnectToFailure.bmdSwitcherConnectToFailureIncompatibleFirmware: 47 | throw new SwitcherLibException("Incompatible firmware"); 48 | 49 | case _BMDSwitcherConnectToFailure.bmdSwitcherConnectToFailureNoResponse: 50 | throw new SwitcherLibException(String.Format("No response from {0}", this.deviceAddress)); 51 | 52 | default: 53 | throw new SwitcherLibException(String.Format("Unknown Error: {0}", ex.Message)); 54 | } 55 | } 56 | catch (Exception ex) 57 | { 58 | throw new SwitcherLibException(String.Format("Unable to connect to switcher: {0}", ex.Message)); 59 | } 60 | } 61 | 62 | public String GetProductName() 63 | { 64 | this.Connect(); 65 | String productName; 66 | this.switcher.GetProductName(out productName); 67 | return productName; 68 | } 69 | 70 | public int GetVideoHeight() 71 | { 72 | this.Connect(); 73 | _BMDSwitcherVideoMode videoMode; 74 | this.switcher.GetVideoMode(out videoMode); 75 | _BMDSwitcherVideoMode switcherVideoMode = videoMode; 76 | switch (switcherVideoMode) 77 | { 78 | case _BMDSwitcherVideoMode.bmdSwitcherVideoMode4KHDp2398: 79 | case _BMDSwitcherVideoMode.bmdSwitcherVideoMode4KHDp24: 80 | case _BMDSwitcherVideoMode.bmdSwitcherVideoMode4KHDp25: 81 | case _BMDSwitcherVideoMode.bmdSwitcherVideoMode4KHDp2997: 82 | return 2160; 83 | 84 | case _BMDSwitcherVideoMode.bmdSwitcherVideoMode720p50: 85 | case _BMDSwitcherVideoMode.bmdSwitcherVideoMode720p5994: 86 | return 720; 87 | 88 | case _BMDSwitcherVideoMode.bmdSwitcherVideoMode1080i50: 89 | case _BMDSwitcherVideoMode.bmdSwitcherVideoMode1080i5994: 90 | case _BMDSwitcherVideoMode.bmdSwitcherVideoMode1080p50: 91 | case _BMDSwitcherVideoMode.bmdSwitcherVideoMode1080p2398: 92 | case _BMDSwitcherVideoMode.bmdSwitcherVideoMode1080p24: 93 | case _BMDSwitcherVideoMode.bmdSwitcherVideoMode1080p25: 94 | case _BMDSwitcherVideoMode.bmdSwitcherVideoMode1080p2997: 95 | case _BMDSwitcherVideoMode.bmdSwitcherVideoMode1080p5994: 96 | return 1080; 97 | 98 | case _BMDSwitcherVideoMode.bmdSwitcherVideoMode525i5994NTSC: 99 | return 480; 100 | default: 101 | throw new SwitcherLibException(String.Format("Unsupported resolution: {0}", videoMode.ToString())); 102 | } 103 | } 104 | 105 | public int GetVideoWidth() 106 | { 107 | int videoHeight = this.GetVideoHeight(); 108 | switch (videoHeight) 109 | { 110 | case 720: 111 | return 1280; 112 | 113 | case 1080: 114 | return 1920; 115 | 116 | case 2160: 117 | return 3840; 118 | 119 | case 480: 120 | return 720; 121 | default: 122 | throw new SwitcherLibException(String.Format("Unsupported video height: {0}", videoHeight.ToString())); 123 | } 124 | } 125 | 126 | public IList GetStills() 127 | { 128 | IList list = new List(); 129 | 130 | IBMDSwitcherMediaPool switcherMediaPool = (IBMDSwitcherMediaPool)this.switcher; 131 | 132 | IBMDSwitcherStills stills; 133 | switcherMediaPool.GetStills(out stills); 134 | 135 | uint count; 136 | stills.GetCount(out count); 137 | for (uint index = 0; index < count; index++) 138 | { 139 | MediaStill mediaStill = new MediaStill(stills, index); 140 | list.Add(mediaStill); 141 | } 142 | 143 | IntPtr mediaPlayerIteratorPtr; 144 | Guid mediaIteratorIID = typeof(IBMDSwitcherMediaPlayerIterator).GUID; 145 | this.switcher.CreateIterator(ref mediaIteratorIID, out mediaPlayerIteratorPtr); 146 | IBMDSwitcherMediaPlayerIterator mediaPlayerIterator = (IBMDSwitcherMediaPlayerIterator)Marshal.GetObjectForIUnknown(mediaPlayerIteratorPtr); 147 | 148 | IBMDSwitcherMediaPlayer mediaPlayer; 149 | mediaPlayerIterator.Next(out mediaPlayer); 150 | int num1 = 1; 151 | while (mediaPlayer != null) 152 | { 153 | _BMDSwitcherMediaPlayerSourceType type; 154 | uint index; 155 | mediaPlayer.GetSource(out type, out index); 156 | if (type == _BMDSwitcherMediaPlayerSourceType.bmdSwitcherMediaPlayerSourceTypeStill) 157 | { 158 | int num2 = (int)index + 1; 159 | foreach (MediaStill mediaStill in list) 160 | { 161 | if (mediaStill.Slot == num2) 162 | { 163 | mediaStill.MediaPlayer = num1; 164 | break; 165 | } 166 | } 167 | } 168 | num1++; 169 | mediaPlayerIterator.Next(out mediaPlayer); 170 | } 171 | return list; 172 | } 173 | 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /SwitcherLib/SwitcherLib.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {5F9F52A6-9EF4-4C71-9A91-F5B864A57ABB} 8 | Library 9 | Properties 10 | SwitcherLib 11 | SwitcherLib 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {8A92B919-156C-4D61-94EF-03F9BE4004B0} 59 | 1 60 | 0 61 | 0 62 | tlbimp 63 | False 64 | True 65 | 66 | 67 | 68 | 75 | -------------------------------------------------------------------------------- /SwitcherLib/SwitcherLibException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SwitcherLib 4 | { 5 | public class SwitcherLibException : Exception 6 | { 7 | public SwitcherLibException() 8 | { 9 | } 10 | 11 | public SwitcherLibException(string message) 12 | : base(message) 13 | { 14 | } 15 | 16 | public SwitcherLibException(string message, Exception inner) 17 | : base(message, inner) 18 | { 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwitcherLib/Upload.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Drawing; 7 | using BMDSwitcherAPI; 8 | using System.IO; 9 | using System.Runtime.InteropServices; 10 | using SwitcherLib.Callbacks; 11 | 12 | namespace SwitcherLib 13 | { 14 | public class Upload 15 | { 16 | private enum Status 17 | { 18 | NotStarted, 19 | Started, 20 | Completed, 21 | } 22 | 23 | private Upload.Status currentStatus; 24 | private String filename; 25 | private int uploadSlot; 26 | private String name; 27 | private Switcher switcher; 28 | private IBMDSwitcherFrame frame; 29 | private IBMDSwitcherStills stills; 30 | private IBMDSwitcherLockCallback lockCallback; 31 | 32 | public Upload(Switcher switcher, String filename, int uploadSlot) 33 | { 34 | this.switcher = switcher; 35 | this.filename = filename; 36 | this.uploadSlot = uploadSlot; 37 | 38 | if (!File.Exists(filename)) 39 | { 40 | throw new SwitcherLibException(String.Format("{0} does not exist", filename)); 41 | } 42 | 43 | this.switcher.Connect(); 44 | this.stills = this.GetStills(); 45 | } 46 | 47 | public bool InProgress() 48 | { 49 | return this.currentStatus == Upload.Status.Started; 50 | } 51 | 52 | public void SetName(String name) 53 | { 54 | this.name = name; 55 | } 56 | 57 | public int GetProgress() 58 | { 59 | if (this.currentStatus == Upload.Status.NotStarted) 60 | { 61 | return 0; 62 | } 63 | if (this.currentStatus == Upload.Status.Completed) 64 | { 65 | return 100; 66 | } 67 | 68 | double progress; 69 | this.stills.GetProgress(out progress); 70 | return (int)Math.Round(progress * 100.0); 71 | } 72 | 73 | public void Start() 74 | { 75 | this.currentStatus = Upload.Status.Started; 76 | this.frame = this.GetFrame(); 77 | this.lockCallback = (IBMDSwitcherLockCallback)new UploadLock(this); 78 | this.stills.Lock(this.lockCallback); 79 | } 80 | 81 | protected IBMDSwitcherFrame GetFrame() 82 | { 83 | IBMDSwitcherMediaPool switcherMediaPool = (IBMDSwitcherMediaPool)this.switcher.GetSwitcher(); 84 | IBMDSwitcherFrame frame; 85 | switcherMediaPool.CreateFrame(_BMDSwitcherPixelFormat.bmdSwitcherPixelFormat8BitARGB, (uint)this.switcher.GetVideoWidth(), (uint)this.switcher.GetVideoHeight(), out frame); 86 | IntPtr buffer; 87 | frame.GetBytes(out buffer); 88 | byte[] source = this.ConvertImage(); 89 | Marshal.Copy(source, 0, buffer, source.Length); 90 | return frame; 91 | } 92 | 93 | protected byte[] ConvertImage() 94 | { 95 | try 96 | { 97 | Bitmap image = new Bitmap(this.filename); 98 | 99 | if (image.Width != this.switcher.GetVideoWidth() || image.Height != this.switcher.GetVideoHeight()) 100 | { 101 | throw new SwitcherLibException(String.Format("Image is {0}x{1} it needs to be the same resolution as the switcher", image.Width.ToString(), image.Height.ToString())); 102 | } 103 | 104 | byte[] numArray = new byte[image.Width * image.Height * 4]; 105 | for (int index1 = 0; index1 < image.Width * image.Height; index1++) 106 | { 107 | Color pixel = this.GetPixel(image, index1); 108 | int index2 = index1 * 4; 109 | numArray[index2] = pixel.B; 110 | numArray[index2 + 1] = pixel.G; 111 | numArray[index2 + 2] = pixel.R; 112 | numArray[index2 + 3] = pixel.A; 113 | } 114 | return numArray; 115 | } 116 | catch (Exception ex) 117 | { 118 | throw new SwitcherLibException(ex.Message, ex); 119 | } 120 | } 121 | 122 | protected Color GetPixel(Bitmap image, int index) 123 | { 124 | int x = index % image.Width; 125 | int y = (index - x) / image.Width; 126 | return image.GetPixel(x, y); 127 | } 128 | 129 | protected IBMDSwitcherStills GetStills() 130 | { 131 | IBMDSwitcherMediaPool switcherMediaPool = (IBMDSwitcherMediaPool)this.switcher.GetSwitcher(); 132 | IBMDSwitcherStills stills; 133 | switcherMediaPool.GetStills(out stills); 134 | return stills; 135 | } 136 | 137 | public void UnlockCallback() 138 | { 139 | this.currentStatus = Upload.Status.Completed; 140 | } 141 | 142 | public void LockCallback() 143 | { 144 | IBMDSwitcherStillsCallback callback = (IBMDSwitcherStillsCallback)new Stills(this); 145 | this.stills.AddCallback(callback); 146 | this.stills.Upload((uint)this.uploadSlot, this.GetName(), this.frame); 147 | } 148 | 149 | public void TransferCompleted() 150 | { 151 | Log.Debug("Completed upload"); 152 | this.stills.Unlock(this.lockCallback); 153 | this.currentStatus = Upload.Status.Completed; 154 | } 155 | 156 | public String GetName() 157 | { 158 | if (this.name != null) 159 | { 160 | return this.name; 161 | } 162 | return Path.GetFileNameWithoutExtension(this.filename); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Blackmagic ATEM C# Library 2 | 3 | ## Introduction 4 | 5 | This library is a collection of tools for working with Blackmagic ATEM video switchers. It is intended to be used as part of automation solutions. 6 | 7 | MediaUpload.exe allows you to upload images to specific slots in a BlackMagic ATEM switcher's media pool. 8 | MediaPool.exe lists all the media in the switcher's media pool. 9 | 10 | ## Usage 11 | 12 | ### Media Upload 13 | 14 | ``` 15 | MediaUpload.exe [options] 16 | 17 | Arguments: 18 | 19 | hostname - The hostname or IP address of the switcher 20 | slot - The slot to upload to 21 | filename - The filename of the image to upload 22 | 23 | Options: 24 | 25 | -h, --help - Help information 26 | -d, --debug - Enable debug output 27 | -v, --version - View version information 28 | -n, --name - Set the name of the image in the media pool 29 | ``` 30 | 31 | Example: 32 | 33 | To upload myfile.png to Slot 1 on a switcher at 192.168.0.254: 34 | 35 | MediaUpload.exe 192.168.0.254 1 myfile.png 36 | 37 | ### Media Pool 38 | 39 | ``` 40 | MediaPool.exe [options] 41 | 42 | Arguments: 43 | 44 | hostname - The hostname or IP of the ATEM switcher 45 | 46 | Options: 47 | 48 | -h, --help - This help message 49 | -d, --debug - Debug output 50 | -v, --version - Version information 51 | -f, --format - The output format. Either xml, csv, json or text 52 | ``` 53 | 54 | Example: 55 | 56 | To see what's in the media pool for a switcher at 192.168.0.254: 57 | 58 | MediaPool.exe 192.168.0.254 59 | 60 | To view the output in JSON format: 61 | 62 | MediaPool.exe -f json 192.168.0.254 63 | 64 | ## Requirements 65 | 66 | - [Microsoft .NET Framework 4.5](http://www.microsoft.com/en-gb/download/details.aspx?id=30653) 67 | - [Blackmagic ATEM Switchers Update 7.3](https://www.blackmagicdesign.com/uk/support/family/atem-live-production-switchers) or later 68 | 69 | ## Supported Image Formats 70 | 71 | The Windows GD+ library is used for image manipulation. This currently supports: 72 | 73 | - PNG 74 | - BMP 75 | - JPEG 76 | - GIF 77 | - TIFF 78 | 79 | Alpha channels are supported and will be included in the images sent to the switcher. 80 | 81 | Images will need to be the same resolution as the switcher. Running in debug mode you can see the detected resolution on the switcher. 82 | 83 | ## Notes 84 | 85 | This has been tested with a Blackmagic Design ATEM Production Studio 4K. I do not have access to any other switchers to test with, but if they use version 6.2 or greater of the SDK, then they should work. 86 | 87 | 88 | ## Contact Details 89 | 90 | If you're using this for anything interesting, I'd love to hear about it. 91 | 92 | - Web: http://www.mintopia.net 93 | - Email: jess@mintopia.net 94 | - Twitter: @MintopiaUK 95 | 96 | - Bitcoin: 1FhMKKabMSJx4M4Trm73JTTrALg7DmxbbP 97 | - Ethereum: 0x8063501c3944846579fb62aaAe3965d933638f35 98 | 99 | ## ChangeLog 100 | 101 | ### Version 2.0.2 - 2018-02-02: 102 | - Add support for NTSC SD 103 | 104 | ### Version 2.0.1 - 2018-02-01: 105 | - Built against Blackmagic Switcher SDK 7.3 106 | 107 | ### Version 2.0.0 - 2014-12-24: 108 | - Rebuilt from decompiled source of original binary 109 | - Added enumerating of the media pool 110 | 111 | ### Version 1.0.1 - 2014-09-22: 112 | - Moved switcher functions into a separate library to allow development of more tools 113 | - Slight change to arguments 114 | - Add support for specifying the name of the image when uploading it 115 | 116 | ### Version 1.0.0 - 2014-09-21: 117 | - Initial version 118 | 119 | ## MIT License 120 | 121 | Copyright (C) 2016 by Jessica Smith 122 | 123 | Permission is hereby granted, free of charge, to any person obtaining a copy 124 | of this software and associated documentation files (the "Software"), to deal 125 | in the Software without restriction, including without limitation the rights 126 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 127 | copies of the Software, and to permit persons to whom the Software is 128 | furnished to do so, subject to the following conditions: 129 | 130 | The above copyright notice and this permission notice shall be included in 131 | all copies or substantial portions of the Software. 132 | 133 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 134 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 135 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 136 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 137 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 138 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 139 | THE SOFTWARE. 140 | --------------------------------------------------------------------------------