I want to get low level access to webcam properties using DirectShow's IAMVideoProcAmp.
There are several Python modules )pywin32, pywintypes, comtypes, win32com, pythoncom) that are used in this context and they seem to be related somehow. But I have no clue where to start.
I found some examples (here, here, here) but I could not figure out how to get a IID / CLSID to use like
import win32com.client
clsid='{9BA05972-F6A8-11CF-A442-00A0C90A8F39}'
ShellWindows=win32com.client.Dispatch(clsid)
or with a clear name like
import win32com.client
xl = win32com.client.Dispatch("Excel.Application")
or
from comtypes import client, GUID
graph = client.CreateObject(some_CLSID)
graph.QueryInterface(...)
Can someone help me with this?
I found another example (dshow.py), but it has some dependencies that I could not find (interfaces, uuids).
This page from Microsoft lists the procedures as
Call QueryInterface on the capture filter for the IAMVideoProcAmp interface.
or
Query the capture filter for the IAMCameraControl.
and states some C++ code for this:
// Query the capture filter for the IAMVideoProcAmp interface.
IAMVideoProcAmp *pProcAmp = 0;
hr = pCap->QueryInterface(IID_IAMVideoProcAmp, (void**)&pProcAmp);
hr = m_pProcAmp->GetRange(VideoProcAmp_Brightness, &Min, &Max, &Step,
&Default, &Flags);
Edit:
I finally found some code that looks good so far:
jaraco
It seem to do exactly what I am trying to write and uses some elements from
DirectShow (see here):
from comtypes.gen.DirectShowLib import (FilterGraph, CaptureGraphBuilder2, ...)
jaraco.video claims to be "a port of the VideoCapture module in pure Python using ctypes and comtypes."
It is using a DirectShow.tlb file (whatever that is) to get the definitions
into comtypes
A type library (.tlb) is a binary file that stores information about a
COM or DCOM object's properties and methods in a form that is
accessible to other applications at runtime.
Identifying required values to replicate the code
At a second glance at the code excerpt at the end of your post, I realize that you only need the IID and not the CLSID for IAMVideoProcAmp in order to acquire an instance of it.
Looking at line 8733 of this source of strmif.h, noted as the required header for the interface, I found that IID_IAMVideoProcAmp is C6E13360-30AC-11d0-A18C-00A0C9118956.
Above this section of strmif.h, you can identify what integers correspond to which properties in the tagVideoProcAmpProperty enum, such as 0 for VideoProcAmp_Brightness. Below this section of strmif.h, you can identify what integers correspond to which functions in the IAMVideoProcAmpVtbl VTable, such as 3 for GetRange. I am unfamiliar with how to interact with COM objects in Python, but in Java you would need to determine these property and function indices in order to replicate the C++ code excerpts that demonstrate how to acquire an instance of IAmVideoProcAmp.
Acquiring an instance of IAMVideoProcAmp
As you may have noticed, the C++ code excerpt invokes QueryInterface on something called pCap and notes that you need to "Query the capture filter for the IAMVideoProcAmp interface." This sibling of the article you linked explains how to do this:
To create a DirectShow capture filter for the device, call the IMoniker::BindToObject method to get an IBaseFilter pointer. Then call IFilterGraph::AddFilter to add the filter to the filter graph:
IBaseFilter *pCap = NULL;
hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pCap);
if (SUCCEEDED(hr))
{
hr = m_pGraph->AddFilter(pCap, L"Capture Filter");
}
Now that you know how to acquire pCap, you notice you need something called pMoniker, which is defined earlier in the same article. The code is fairly long so I omit it here.
Doing all of this in Python
As I noted earlier, I have never used any Python COM library, so I cannot easily whip up an example, but your goal should be to replicate in Python the function invocations made in the C++ examples to acquire an instance of IAMVideoProcAmp and modifying them to suit your needs.
I finally found some example library that is working:
jaraco
It does exactly what I am trying to achive and uses some elements from
DirectShow (see here):
from comtypes.gen.DirectShowLib import (FilterGraph, CaptureGraphBuilder2, ...)
jaraco.video claims to be "a port of the VideoCapture module in pure Python using ctypes and comtypes."
It is using a DirectShow.tlb file (whatever that is) to get the definitions
into comtypes
A type library (.tlb) is a binary file that stores information about a
COM or DCOM object's properties and methods in a form that is
accessible to other applications at runtime.
The imports are auto-generated in __init__.py and can be used easily:
from api.objects import ..., IMediaControl, IAMVideoProcAmp, IAMCameraControl, ...
and can be used
def _get_camera_control(self):
return self._get_graph_builder_interface(IAMCameraControl)
def get_camera_control_property(self, i):
video_properties = self._get_camera_control()
return video_properties.Get(i)
Then you can use the functions in combination with the enum stated in the docs, e.g.
# CameraControl_Exposure = 4
print(d.get_camera_control_property(4))
Related
How do I access the constants available as file formats when saving a Powerpoint presentation through comtypes?
In the following example 32 is used as the format but I would like to use the constants listed here) or at least find a documented list with value for each constant.
For Word there is this list that also contains the value for each constant.
import comtypes.client
powerpoint = comtypes.client.CreateObject("Powerpoint.Application")
pres = powerpoint.Presentations.Open(input_path)
pres.SaveAs(output_path, 32)
You can access all enum names associated with the COM object you loaded via the comtypes.client.Constants() class; pass it the PowerPoint.Application COM object you created:
from comtypes.client import Constants, CreateObject
powerpoint = CreateObject("Powerpoint.Application")
pp_constants = Constants(powerpoint)
pres = powerpoint.Presentations.Open(input_path)
pres.SaveAs(output_path, pp_constants.ppSaveAsPDF)
The Constants instance loads the underlying Type Library and dynamically translates attribute lookups to typelib access. It is not included in the comtypes documentation for some obscure reason, even though it was added nearly 10 years ago now.
Another option is to access the attributes on a generated module in the generated type library, as shown in the Properties with arguments (named properties) section. This would give you access to any of the constants associated with the Powerpoint IDL, including auto completion support IDEs (once generated by accessing the PowerPoint.Application object the first time around).
The module is generated automatically when you use CreateObject() if type information is exposed on the object that's being created; this is definitely the case for 'Powerpoint.Application' as you don't set an interface explicitly. Automatic interface selection only works if there is type information available.
Enumeration names are added to the generated module at the top level, so directly use those:
import comtypes.client
powerpoint = comtypes.client.CreateObject("Powerpoint.Application")
# only import the generated namespace after the com object has been created
# at least once. The generated module is cached for future runs.
from comtypes.gen import PowerPoint
pres = powerpoint.Presentations.Open(input_path)
pres.SaveAs(output_path, PowerPoint.ppSaveAsPDF)
The short name of the type library can be found in a VBA Object Browser; the screenshot in Steve Rindsberg's answer shows that for the PpSaveAsFileType enum that's PowerPoint. I believe the same name is also used in the documentation for the ppSaveAsFileType enum; note the (PowerPoint) addition to the documentation title.
You can also use the GUID of the type library, plus version number, but that doesn't quite roll of the keyboard if you have to type that by hand.
You can use from comtypes.gen import PowerPoint; help(PowerPoint) to see what names have been defined if you need a reminder, or just reference the Microsoft documentation.
Either method avoids having to use magic numbers; the type library definition itself gives you the symbolic names.
If you find any code examples using win32com instead, then any use of win32com.client.constants attributes translates directly to comtypes.client.Constant(...) or comtypes.gen.<module> attributes.
I don't have access to a Windows setup to actually test any of this, I'm deducing information from reading documentation and the source code of comtypes.
Here is the list from Microsoft which contains the values of each constant:
https://learn.microsoft.com/en-us/office/vba/api/powerpoint.ppsaveasfiletype
Assuming you have a copy of PowerPoint, start it, press ALT+F11 to open the VBA editor, press F2 to open the Object Browser then search on SaveAs to get this list. Click on any constant name to see the value of the constant at the bottom of the dialog.
In python 3.6 I'm using an COM interface to communicate with Excel and Word, in this case Word for automated reporting as the data processing is done in python.
I don't know how python can get the members of such a COM object similar to the use of the dir() function.
(Previously using Matlab, i would use the .get or .invoke methods to get this)
So the code would be:
def wordOpen(wordfile):
pythoncom.CoInitializeEx(pythoncom.COINIT_APARTMENTTHREADED)
wApp = win32com.client.DispatchEx('Word.Application')
wDoc = wApp.Documents.Open(wordfile)
wApp.Visible = 1
wApp.Activate
wRange = wDoc.Content
return wApp, wDoc, wRange
wApp, wDoc, wRange = wordOpen(wordfile)
dir(wDoc)
.. which does not provide me the list of methods and properties of the Word document object (similar for wApp and wRange).
Similarly I've tried inspect.getmembers(wDoc) but this also does not provide the list of methods/properties that I'm looking for.
For the same thing when communicating with Excel, I used to go into the VBA editor and get a list there, but is there any method to do this from the IPython
console directly?
Did some more searching and figured it out myself. The above example uses 'dynamic dispatching' and we want to use 'static dispatching' instead. That does not change the code, it only entails a one-off action.
Dynamic is a quick-and-dirty way to create these objects, and then python knows nothing about the type of object.
Static means that you have to run makepy.py from the command line to create a type library (in this case for Word objects), and after that has been done once, python knows all the info about the object every next time it launches the COM object.
The procedure is very simple and comparable to the VBA action of adding References to your project.
I am writing a python3 + webkit2 + gtk3 based GUI. Everything works fine, but when I tried to use WebKit2.WebView.run_javascript_finish() in a callback function, getting the return (a WebKit2.JavascriptResult objetc), e extrat the value with WebKit2.JavascriptResult.get_value(), I received a "TypeError: Couldn't find foreign struct converter for 'JavaScriptCore.Value'".
I have a "from gi.repository import JavaScriptCore" on the imports, and I dont know why this error is occuring.
ps: Sorry about my terrible english.
Unfortunately, using the JavaScriptCore library isn't supported in Python.
The definitions in gi.repository.JavaScriptCore only include opaque definitions for JavaScriptCore.Value and JavaScriptCore.GlobalContext in order to provide type information for functions that use those types; as you can verify by looking in /usr/share/gir-1.0/JavaScriptCore-4.0.gir. But as you've discovered, you can't actually call those functions because there's no information on how to convert those types to native Python objects.
I recommend writing a function in C which takes in your WebKit2.JavaScriptResult and fetches whatever information you need from it, and then exporting that function in its own private GIR library. So you would not use any JavaScriptCore types in your Python code. This is what it might look like:
from gi.repository import MyAppPrivate
# ...
def on_javascript_finish(obj, res):
result = obj.run_javascript_finish(res)
my_real_results = MyAppPrivate.process(result)
Using python's wmi module to create a vss snapshot, I've found that the parameters don't work unless I reverse them:
import wmi
def vss_create():
shadow_copy_service = wmi.WMI(moniker='winmgmts:\\\\.\\root\\cimv2:Win32_ShadowCopy')
res = shadow_copy_service.Create('ClientAccessible', 'C:\\')
In the msdn docs, the function is instead supposed to be used this way:
Win32_ShadowCopy.Create("C:\\", "ClientAccessible");
Why is this the case, and is there a way to use the intended order?
Summary
It looks like the parameter ordering for wmi object's methods is reversed from normal by the PyWin32 layer, and this behaviour has been present for at least five years. The relevant wmi spec says that a wmi client can pass the parameters in any order, so PyWin32 is not 'wrong' to do this, although I can't tell if it's deliberate or accident. I speculate that it's unlikely to change, for backwards compatibility reasons, but you can work around this and put the parameters in the order you want by specifying them as keyword parameters: Create(Volume=, Context=).
Details
NB. In the below details, I'm trying to go down in layers from the Python WMI module code to WMI objects accessed by COM in PyWin32 code, to WMI objects as documented and used in other languages, to WMI object specification by MOF files, to specification documents. There's several layers and I write "WMI" a lot, meaning different things at different layers.
When you say "Python's wmi module" do you mean Tim Golden's Python WMI module (link to source code) that builds on PyWin32?
When you get a Python WMI object from the wmi module, the initialization steps it goes through are inside the class _wmi_object, and include querying the underlying wmi object for its available methods:
for m in ole_object.Methods_:
self.methods[m.Name] = None
I'm going to skip beneath Python's wmi module, and use PyWin32 directly to look at what you get when querying a WMI COM object for its available methods:
>>> from win32com.client import GetObject
>>> vss = GetObject('winmgmts:\\\\.\\root\\cimv2:Win32_ShadowCopy')
>>> [method.Name for method in list(vss.Methods_)]
[u'Create', u'Revert']
And we see the Win32_ShadowCopy object has the methods Create and Revert. So that's where the Python wmi wrapper first learns about the Create method you are using.
From there, the Python WMI wrapper class does some setup work which I haven't traced through fully, but it seems to initialize class _wmi_method once for each available method of the COM object. This class includes these initialization steps:
self.method = ole_object.Methods_ (method_name)
self.in_parameter_names = [(i.Name, i.IsArray) for i in self.in_parameters.Properties_]
A list comprehension to get the available parameters for each method. Going back to my testing to explore that without the Python WMI layer, it gives output like this:
>>> CreateMethod = vss.Methods_('Create')
>>> [n.Name for n in list(CreateMethod.InParameters.Properties_)]
[u'Context', u'Volume']
This example test shows the PyWin32 later, the COM object for Win32_ShadowCopy, the Create method - lists its available parameters in the order you are seeing - the "wrong" order. The Python WMI layer is picking up that ordering.
When you call the Win32_ShadowCopy object's Create() method through Python WMI's wrapper, the _wmi_method does this:
def __call__ (self, *args, **kwargs):
for n_arg in range (len (args)):
arg = args[n_arg]
parameter = parameters.Properties_[n_arg]
parameter.Value = arg
In other words; it pairs up the parameters you pass in (*args) with the stored parameter list, one by one, taking the parameters in the order you pass them, and pairing them with the method parameters in the order WMI returned them - i.e. it's not intelligent, it just links the first parameter you enter with 'Context' and the second with 'Volume' and gets them backwards, and your code crashes.
The call method also includes Python's **kwargs parameter which takes all given keywords, suggesting you can do
Create(Volume='C:\\', Context="ClientAccessible")
and put them in the order you want by using them as keyword arguments. (I haven't tried).
I have tried tracing the .Properties_ lookup through PyWin32com to try and identify where the ordering comes from at the lower layers, and it goes through a long chain of dynamic and cached lookups. I can't see what happens and I don't understand enough COM or PyWin32 to know what kinds of things to be looking for, so that's a dead end for me.
Taking a different approach and trying to find out from the WMI object setup files where the ordering comes from: run mofcomp.exe which ships with Windows and processes Managed Object Format (MOF) files... click Connect, Create Class "Win32_ShadowCopy"; Click the "Create" method in the methods list, then click the "Edit Method" button; then click "Edit Input Arguments" then click "Show MOF", and get this result:
[abstract]
class __PARAMETERS
{
[in, ID(0): DisableOverride ToInstance] string Volume;
[in, ID(1): DisableOverride ToInstance] string Context = "ClientAccessible";
};
That's the "correct" order of the parameters coming out of the Windows MOF files, with numeric IDs for the parameters - implying they have a correct ordering 0, 1, etc.
c:\windows\system32\wbem\vss.mof, the MOF file which appears to cover the Volume Shadow Copy objects contains this:
[static,implemented,constructor] uint32 Create([in] string Volume,[in] string Context = "ClientAccessible",[out] string ShadowID);
and the PowerShell example in the comments at this MSDN link includes $class.create("C:\", "ClientAccessible").
So those three things all tie up with the same ordering and implies there is a correct or standard ordering.
That leaves me thinking of these possibilities:
There is ordering information coming out of PythonCOM and the wmi module should look at it, but doesn't. - I have looked around quickly, and can't find ID / ordering data with the parameters list, so that seems unlikely.
There is ordering information somewhere unknown to me which the PyWin32 COM layer should be looking at but doesn't. - Not sure here.
There is no official ordering. Trying to confirm this point, we get a fun chain:
What is WMI? Microsoft's implementation of standard management frameworks WBEM and CIM, specified by the DTMF. (DTMF = Distributed Management Task Force, WBEM is Web Based Enterprise Management and CIM is the Common Information Model).
MOF is the Managed Object Format, a text representation of CIM
This document: http://www.dmtf.org/sites/default/files/standards/documents/DSP0221_3.0.0.pdf appears to be the MOF specification. Check section 7.3.3 Class Declaration, from page 18:
line 570:
"A method can have zero or more parameters".
lines 626 through 628:
Method parameters are identified by name and not by position and
clients invoking a method can pass the corresponding arguments in
any order. Therefore parameters with default values can be added to
the method signature at any position.
I don't know for sure if that's an authoritative and current specification, nor have I read all of it looking for exceptions, but it sounds like you should use named parameters.
The WMI objects and methods have a MOF definition, and the MOF specification says you shouldn't rely on the parameter ordering; however, accessing the WMI objects via COM via PyWin32 is showing a different ordering to (MSDN docs, the MOF file and the PowerShell examples). I still don't know why.
And Googling that leads me to this mailing list post by Tim Golden, author of the Python wmi module, saying basically the same thing as I've just found, except five years ago:
the method definition picks up the parameters in the order in which WMI returns them [..]
I've got no idea if there is any guarantee about the order of parameters [..]
Glancing at a few other method definitions, it does seem as though WMI is consistently returning params in the reverse order of their definition in the MOF.
At this point, it looks like PyWin32 is returning a reversed list to the typical Windows parameter order, but is that a bug if the CIM managed object method parameter list specification document explicitly says don't rely on the parameter ordering?
I'm currently wondering how to list the constants in win32com in python,
for example using excel win32com.client.Dispatch('Excel.Application')
Is there a way to display all constants using win32com.client.Constants ?
Or does someone know where i could find win32com's documentation ? Because all the links I found are dead ...
To find out what Com applications you can use... See http://timgolden.me.uk/pywin32-docs/html/com/win32com/HTML/QuickStartClientCom.html
Basically you can't know for sure. Sorry.
Each computer will have a different list based on the software installed, the webpage I linked suggests using pyWins ComBrowser, however I haven't ever found it that useful.
My normal approach is
'I want to interface with application X in Python... lets google "python com X" and see what comes up'
or
'I want to interface with application X in Python.. lets look through the documentation of AppX for references to COM'
Afterall you'll want to have some form of documentation to that programmes COM interface in order to be able to do anything meaningful with the program (other than opening it).
The answer given by #CastleH in his comment pointed me in the right direction: help() helped me a lot, but you have to use it on module-level!
For example,
import win32print
help(win32print)
gives you all constants in win32print. You can do the same for every module in win32com (for a full list of modules see the win32com documentation, section modules).
Using help(win32com)did not help me a lot since it is mainly listing the packages
To list available constants, you can do something like:
import win32com
for k, v in win32com.client.constants.__dicts__[0].items():
print("{:<45}: {}".format(k, v))
# __module__ : win32com.gen_py.00020813-0000-0000-C000-000000000046x0x1x9
# xl3DBar : -4099
# xl3DEffects1 : 13
# xl3DEffects2 : 14
# xl3DSurface : -4103
# ...
The best resource for pywin32, even in 2020 (16+ years after its creation), is the source code1.
It turns out that win32com.client.constants is an instance of the Constants class:
class Constants:
"""A container for generated COM constants.
"""
def __init__(self):
self.__dicts__ = [] # A list of dictionaries
def __getattr__(self, a):
for d in self.__dicts__:
if a in d:
return d[a]
raise AttributeError(a)
# And create an instance.
constants = Constants()
The constants are all stored in self.__dicts__ which is a list of dictionaries. That list happens to contain only a single dictionary.
Knowing this, you can iterate through the dictionary (as done above) to see what constants are available.
1 For dead links, try The Internet Archive's Wayback Machine. There are plugins for various browsers which make searching for archived versions of a page easy or automatic.