11using System ;
22using System . IO ;
33using System . Reflection ;
4+ using System . Security ;
45using System . Runtime . Versioning ;
56using System . Text ;
67using System . Text . Json ;
@@ -25,6 +26,8 @@ class Program
2526 typeof ( GetCameraNameOptions ) ,
2627 typeof ( SetCameraNameOptions ) ,
2728 typeof ( ClearCameraNameOptions ) ,
29+ typeof ( GetDirectShowCameraNameOptions ) ,
30+ typeof ( SetDirectShowCameraNameOptions ) ,
2831 typeof ( SwapPresetNamesOptions ) ,
2932 typeof ( ConfigOptions ) ,
3033 typeof ( RestoreHomeOptions ) ,
@@ -116,6 +119,8 @@ static int RunParsed(object parsed) =>
116119 GetCameraNameOptions options => GetCameraName ( ToOptions ( options ) ) ,
117120 SetCameraNameOptions options => SetCameraName ( ToOptions ( options ) ) ,
118121 ClearCameraNameOptions options => ClearCameraName ( ToOptions ( options ) ) ,
122+ GetDirectShowCameraNameOptions options => GetDirectShowCameraName ( ToOptions ( options ) ) ,
123+ SetDirectShowCameraNameOptions options => SetDirectShowCameraName ( ToOptions ( options ) , options . AcknowledgeWarning ) ,
119124 SwapPresetNamesOptions options => SwapPresetNames ( RequireSlot ( options . SlotA , "--slot-a" ) , RequireSlot ( options . SlotB , "--slot-b" ) ) ,
120125 ConfigOptions options => RunConfig ( ToOptions ( options ) ) ,
121126 RestoreHomeOptions options => RestoreHome ( ResolveCamera ( ToOptions ( options ) ) , ParseTarget ( options . TargetName ) ) ,
@@ -425,6 +430,48 @@ static int ClearCameraName(Options options)
425430 return Ok ( ) ;
426431 }
427432
433+ static int GetDirectShowCameraName ( Options options )
434+ {
435+ var camera = ResolveSingleCameraInfo ( options , requireExplicitSelector : true ) ;
436+ Console . WriteLine ( CameraBackend . GetDirectShowCameraName ( camera . MonikerString ) ) ;
437+ return 0 ;
438+ }
439+
440+ static int SetDirectShowCameraName ( Options options , bool acknowledgeWarning )
441+ {
442+ if ( string . IsNullOrWhiteSpace ( options . FriendlyName ) )
443+ throw new ArgumentException ( "set-directshow-camera-name requires --friendlyname." ) ;
444+
445+ var camera = ResolveSingleCameraInfo ( options , requireExplicitSelector : true ) ;
446+ if ( ! acknowledgeWarning )
447+ RequireDirectShowRenameConfirmation ( ) ;
448+
449+ try
450+ {
451+ CameraBackend . SetDirectShowCameraName ( camera . MonikerString , options . FriendlyName ) ;
452+ }
453+ catch ( UnauthorizedAccessException ex )
454+ {
455+ throw new InvalidOperationException ( "Administrator rights are required to set the DirectShow camera name. Start the console as administrator or use an elevated shell." , ex ) ;
456+ }
457+ catch ( SecurityException ex )
458+ {
459+ throw new InvalidOperationException ( "Administrator rights are required to set the DirectShow camera name. Start the console as administrator or use an elevated shell." , ex ) ;
460+ }
461+
462+ return Ok ( ) ;
463+ }
464+
465+ static void RequireDirectShowRenameConfirmation ( )
466+ {
467+ Console . Error . WriteLine ( "Warning:" ) ;
468+ Console . Error . WriteLine ( DirectShowRenameWarningText ) ;
469+ Console . Error . Write ( "Type YES to continue: " ) ;
470+ var answer = Console . ReadLine ( ) ;
471+ if ( ! string . Equals ( answer , "YES" , StringComparison . Ordinal ) )
472+ throw new OperationCanceledException ( "DirectShow camera rename was not confirmed." ) ;
473+ }
474+
428475 static int RunConfig ( Options options )
429476 {
430477 if ( options . ExportConfig && ! string . IsNullOrWhiteSpace ( options . ImportConfigPath ) )
@@ -724,6 +771,61 @@ static string ResolveCamera(Options options)
724771 return ( null , null ) ;
725772 }
726773
774+ static CameraInfo ResolveSingleCameraInfo ( Options options , bool requireExplicitSelector )
775+ {
776+ var selectorCount =
777+ ( ! string . IsNullOrWhiteSpace ( options . Camera ) ? 1 : 0 ) +
778+ ( ! string . IsNullOrWhiteSpace ( options . DevicePath ) ? 1 : 0 ) +
779+ ( options . Slot . HasValue ? 1 : 0 ) ;
780+ if ( selectorCount > 1 )
781+ throw new ArgumentException ( "Use only one camera selector: --camera, --device-path, or --slot." ) ;
782+ if ( requireExplicitSelector && selectorCount == 0 )
783+ throw new ArgumentException ( "Use --camera, --device-path, or --slot to select the camera." ) ;
784+
785+ var cameras = CameraBackend . Enumerate ( ) ;
786+ if ( cameras . Count == 0 )
787+ throw new InvalidOperationException ( "No camera found." ) ;
788+
789+ if ( options . Slot . HasValue )
790+ {
791+ var slot = RequireSlot ( options . Slot , "--slot" ) ;
792+ if ( cameras . Count < slot )
793+ throw new InvalidOperationException ( $ "Camera slot { slot } is not available. Found { cameras . Count } camera(s).") ;
794+ return RequireDevicePath ( cameras [ slot - 1 ] ) ;
795+ }
796+
797+ if ( ! string . IsNullOrWhiteSpace ( options . DevicePath ) )
798+ {
799+ var match = cameras
800+ . Where ( camera => ! string . IsNullOrWhiteSpace ( camera . MonikerString ) &&
801+ string . Equals ( camera . MonikerString , options . DevicePath , StringComparison . OrdinalIgnoreCase ) )
802+ . ToList ( ) ;
803+ if ( match . Count == 1 )
804+ return RequireDevicePath ( match [ 0 ] ) ;
805+
806+ return new CameraInfo { Name = options . DevicePath , MonikerString = options . DevicePath } ;
807+ }
808+
809+ if ( ! string . IsNullOrWhiteSpace ( options . Camera ) )
810+ {
811+ var matches = cameras . Where ( camera => CameraMatches ( camera , options . Camera ) ) . ToList ( ) ;
812+ if ( matches . Count == 0 )
813+ throw new InvalidOperationException ( $ "Camera '{ options . Camera } ' not found.") ;
814+ if ( matches . Count > 1 )
815+ throw new InvalidOperationException ( $ "Camera selector '{ options . Camera } ' is ambiguous. Use --device-path instead.") ;
816+ return RequireDevicePath ( matches [ 0 ] ) ;
817+ }
818+
819+ return RequireDevicePath ( cameras [ 0 ] ) ;
820+ }
821+
822+ static CameraInfo RequireDevicePath ( CameraInfo camera )
823+ {
824+ if ( string . IsNullOrWhiteSpace ( camera . MonikerString ) )
825+ throw new InvalidOperationException ( $ "Camera '{ camera . Name } ' does not provide a device path.") ;
826+ return camera ;
827+ }
828+
727829 static bool CameraMatches ( CameraInfo camera , string query ) =>
728830 camera . Name . Contains ( query , StringComparison . OrdinalIgnoreCase ) ||
729831 ( ! string . IsNullOrWhiteSpace ( camera . MonikerString ) && camera . MonikerString . Contains ( query , StringComparison . OrdinalIgnoreCase ) ) ;
@@ -1160,6 +1262,21 @@ sealed class ClearCameraNameOptions
11601262 public int ? Slot { get ; set ; }
11611263 }
11621264
1265+ [ Verb ( "get-directshow-camera-name" , HelpText = "Print the Windows DirectShow camera name." ) ]
1266+ sealed class GetDirectShowCameraNameOptions : CameraSelectionOptions
1267+ {
1268+ }
1269+
1270+ [ Verb ( "set-directshow-camera-name" , HelpText = "Set the Windows DirectShow camera name." ) ]
1271+ sealed class SetDirectShowCameraNameOptions : CameraSelectionOptions , IFriendlyNameOptions
1272+ {
1273+ [ Option ( 'n' , "friendlyname" , Required = true , HelpText = "DirectShow friendly name to write." ) ]
1274+ public string ? FriendlyName { get ; set ; }
1275+
1276+ [ Option ( "acknowledge-warning" , HelpText = "Skip the interactive registry risk confirmation." ) ]
1277+ public bool AcknowledgeWarning { get ; set ; }
1278+ }
1279+
11631280 [ Verb ( "swap-preset-names" , HelpText = "Swap preset friendly names between two camera slots." ) ]
11641281 sealed class SwapPresetNamesOptions
11651282 {
@@ -1316,4 +1433,7 @@ sealed class Options
13161433 public int ? X { get ; set ; }
13171434 public int ? Y { get ; set ; }
13181435 }
1436+
1437+ private const string DirectShowRenameWarningText =
1438+ "This feature is provided \" as is\" without any warranty. The authors are not responsible for any damage to the system or the cameras. Use this feature at your own risk. It is recommended to create a backup of the registry path HKEY_LOCAL_MACHINE\\ SYSTEM\\ CurrentControlSet\\ Control\\ DeviceClasses\\ {65e8773d-8f56-11d0-a3b9-00a0c9223196} before using this feature, especially if you are not sure what you are doing." ;
13191439}
0 commit comments