Darmowe skrypty do vMix

Ducking Audio inputa 1 i 2 wywoływany BUSEM F i G

Gotowy skrypt do vMix. Działa w pętli, sprawdza poziom audio na wybranych BUS-ach i płynnie przycisza wskazane inputy.

Dim engageThreshold As Double = 50.0
Dim releaseThreshold As Double = 35.0

Dim normalVolume1 As Double = 100.0
Dim normalVolume2 As Double = 100.0

Dim duckPercent As Double = 0.5
Dim fadeMs As Integer = 500
Dim releaseHoldMs As Integer = 700
Dim checkMs As Integer = 100

Dim ducked1 As Boolean = False
Dim lastSignalTime1 As DateTime = DateTime.Now

Dim ducked2 As Boolean = False
Dim lastSignalTime2 As DateTime = DateTime.Now

Do While True

    Dim x As New System.Xml.XmlDocument
    x.LoadXml(API.XML())

    ' BUS F -> INPUT 1
    Dim busFNode = x.SelectSingleNode("//audio/busF")

    If busFNode IsNot Nothing Then

        Dim meterLF As Double = Double.Parse(busFNode.Attributes("meterF1").Value, Globalization.CultureInfo.InvariantCulture)
        Dim meterRF As Double = Double.Parse(busFNode.Attributes("meterF2").Value, Globalization.CultureInfo.InvariantCulture)

        Dim busLevelF As Double = Math.Max(Math.Pow(meterLF, 0.25) * 100.0, Math.Pow(meterRF, 0.25) * 100.0)

        If busLevelF > engageThreshold Then

            lastSignalTime1 = DateTime.Now

            If ducked1 = False Then
                API.Function("SetVolumeFade", Input:="1", Value:=CInt(normalVolume1 * duckPercent).ToString() & "," & fadeMs.ToString())
                ducked1 = True
            End If

        End If

        If busLevelF < releaseThreshold Then

            If ducked1 = True AndAlso DateTime.Now.Subtract(lastSignalTime1).TotalMilliseconds > releaseHoldMs Then
                API.Function("SetVolumeFade", Input:="1", Value:=CInt(normalVolume1).ToString() & "," & fadeMs.ToString())
                ducked1 = False
            End If

        End If

    End If

    ' BUS G -> INPUT 2
    Dim busGNode = x.SelectSingleNode("//audio/busG")

    If busGNode IsNot Nothing Then

        If busGNode.Attributes("meterF1") IsNot Nothing AndAlso busGNode.Attributes("meterF2") IsNot Nothing Then

            Dim meterLG As Double = Double.Parse(busGNode.Attributes("meterF1").Value, Globalization.CultureInfo.InvariantCulture)
            Dim meterRG As Double = Double.Parse(busGNode.Attributes("meterF2").Value, Globalization.CultureInfo.InvariantCulture)

            Dim busLevelG As Double = Math.Max(Math.Pow(meterLG, 0.25) * 100.0, Math.Pow(meterRG, 0.25) * 100.0)

            If busLevelG > engageThreshold Then

                lastSignalTime2 = DateTime.Now

                If ducked2 = False Then
                    API.Function("SetVolumeFade", Input:="2", Value:=CInt(normalVolume2 * duckPercent).ToString() & "," & fadeMs.ToString())
                    ducked2 = True
                End If

            End If

            If busLevelG < releaseThreshold Then

                If ducked2 = True AndAlso DateTime.Now.Subtract(lastSignalTime2).TotalMilliseconds > releaseHoldMs Then
                    API.Function("SetVolumeFade", Input:="2", Value:=CInt(normalVolume2).ToString() & "," & fadeMs.ToString())
                    ducked2 = False
                End If

            End If

        End If

    End If

    Sleep(checkMs)

Loop

Jak działa ten skrypt?

  1. Skrypt co 100 ms pobiera z vMix aktualny XML, czyli informacje o projekcie, inputach i poziomach audio.
  2. Następnie sprawdza poziom sygnału na BUS F. Jeżeli poziom przekroczy 50%, skrypt przycisza Input 1 do połowy głośności.
  3. Jeżeli poziom na BUS F spadnie poniżej 35% i minie krótki czas podtrzymania, Input 1 wraca płynnie do 100%.
  4. Analogicznie działa druga część: BUS G steruje przyciszeniem Input 2.
  5. Zmienna ducked1 i ducked2 pilnuje, żeby input nie był ściszany wielokrotnie coraz niżej. Dzięki temu poziom wraca do normalnej wartości.

Auto Cut by Audio — automatyczne przełączanie kamer

Gotowy skrypt do vMix, który na podstawie poziomów audio automatycznie przełącza ujęcia między planem szerokim a close-upami rozmówców.

' ==========================================
' AUTO CUT BY AUDIO - CLOSE-UP PRIORITY VERSION
' BUS A Left  -> LEFT_CLOSE_INPUT
' BUS A Right OR BUS B Left -> RIGHT_CLOSE_INPUT
' Priorytet: trzymanie close-upów
' Szeroki: rzadko lub gdy mówią obie strony
' ==========================================

Dim WIDE_INPUT As String = "4"
Dim LEFT_CLOSE_INPUT As String = "5"
Dim RIGHT_CLOSE_INPUT As String = "6"

Dim THRESHOLD_SPEECH As Double = 0.060
Dim THRESHOLD_DOMINANCE As Double = 0.010
Dim THRESHOLD_BOTH_TALK As Double = 0.018

Dim MIN_SHOT_MS As Integer = 2600

Dim WIDE_INSERT_EVERY_MS As Integer = 60000
Dim WIDE_INSERT_DURATION_MS As Integer = 3000

Dim SPEAKER_HOLD_MS As Integer = 500
Dim PAUSE_HOLD_MS As Integer = 5000

Dim LOOP_MS As Integer = 150
Dim TRANSITION_NAME As String = "CutDirect"

Dim currentShot As String = WIDE_INPUT
Dim target As String = WIDE_INPUT
Dim lastCloseShot As String = LEFT_CLOSE_INPUT

Dim lastSwitchTime As Date = Date.Now
Dim lastWideInsertTime As Date = Date.Now
Dim leftCandidateSince As Date = Date.MinValue
Dim rightCandidateSince As Date = Date.MinValue
Dim wideCandidateSince As Date = Date.MinValue
Dim silenceSince As Date = Date.MinValue
Dim forceWideUntil As Date = Date.MinValue

Dim rnd As New Random()

API.Function(TRANSITION_NAME, Input:=WIDE_INPUT)

Do While True

    Dim xml As String = API.XML()
    Dim x As New System.Xml.XmlDocument()
    x.LoadXml(xml)

    Dim busALeftNode As System.Xml.XmlNode = x.SelectSingleNode(".//audio/busA/@meterF1")
    If busALeftNode Is Nothing Then
        busALeftNode = x.SelectSingleNode(".//audio/busa/@meterF1")
    End If

    Dim busARightNode As System.Xml.XmlNode = x.SelectSingleNode(".//audio/busA/@meterF2")
    If busARightNode Is Nothing Then
        busARightNode = x.SelectSingleNode(".//audio/busa/@meterF2")
    End If

    Dim busBLeftNode As System.Xml.XmlNode = x.SelectSingleNode(".//audio/busB/@meterF1")
    If busBLeftNode Is Nothing Then
        busBLeftNode = x.SelectSingleNode(".//audio/busb/@meterF1")
    End If

    Dim leftLevel As Double = 0.0
    Dim busARightLevel As Double = 0.0
    Dim busBLeftLevel As Double = 0.0
    Dim rightLevel As Double = 0.0

    If Not busALeftNode Is Nothing Then
        Double.TryParse(busALeftNode.Value, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, leftLevel)
    End If

    If Not busARightNode Is Nothing Then
        Double.TryParse(busARightNode.Value, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, busARightLevel)
    End If

    If Not busBLeftNode Is Nothing Then
        Double.TryParse(busBLeftNode.Value, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, busBLeftLevel)
    End If

    rightLevel = Math.Max(busARightLevel, busBLeftLevel)

    Dim nowTime As Date = Date.Now
    Dim diff As Double = Math.Abs(leftLevel - rightLevel)

    Dim leftSpeaking As Boolean = leftLevel >= THRESHOLD_SPEECH
    Dim rightSpeaking As Boolean = rightLevel >= THRESHOLD_SPEECH
    Dim bothSpeaking As Boolean = leftSpeaking And rightSpeaking
    Dim silence As Boolean = (Not leftSpeaking And Not rightSpeaking)

    target = currentShot

    If nowTime < forceWideUntil Then
        target = WIDE_INPUT
    Else

        ' Rzadki szeroki "oddech"
        If (nowTime - lastWideInsertTime).TotalMilliseconds >= WIDE_INSERT_EVERY_MS Then
            If rnd.Next(0, 100) < 50 Then
                forceWideUntil = nowTime.AddMilliseconds(WIDE_INSERT_DURATION_MS)
                lastWideInsertTime = nowTime
                target = WIDE_INPUT
            End If
        End If

        If nowTime >= forceWideUntil Then

            If bothSpeaking And diff <= THRESHOLD_BOTH_TALK Then

                If wideCandidateSince = Date.MinValue Then
                    wideCandidateSince = nowTime
                End If
                leftCandidateSince = Date.MinValue
                rightCandidateSince = Date.MinValue
                silenceSince = Date.MinValue

                If (nowTime - wideCandidateSince).TotalMilliseconds >= SPEAKER_HOLD_MS Then
                    target = WIDE_INPUT
                End If

            ElseIf leftSpeaking And (leftLevel - rightLevel) >= THRESHOLD_DOMINANCE Then

                If leftCandidateSince = Date.MinValue Then
                    leftCandidateSince = nowTime
                End If
                rightCandidateSince = Date.MinValue
                wideCandidateSince = Date.MinValue
                silenceSince = Date.MinValue

                If (nowTime - leftCandidateSince).TotalMilliseconds >= SPEAKER_HOLD_MS Then
                    target = LEFT_CLOSE_INPUT
                    lastCloseShot = LEFT_CLOSE_INPUT
                End If

            ElseIf rightSpeaking And (rightLevel - leftLevel) >= THRESHOLD_DOMINANCE Then

                If rightCandidateSince = Date.MinValue Then
                    rightCandidateSince = nowTime
                End If
                leftCandidateSince = Date.MinValue
                wideCandidateSince = Date.MinValue
                silenceSince = Date.MinValue

                If (nowTime - rightCandidateSince).TotalMilliseconds >= SPEAKER_HOLD_MS Then
                    target = RIGHT_CLOSE_INPUT
                    lastCloseShot = RIGHT_CLOSE_INPUT
                End If

            ElseIf silence Then

                If silenceSince = Date.MinValue Then
                    silenceSince = nowTime
                End If
                leftCandidateSince = Date.MinValue
                rightCandidateSince = Date.MinValue
                wideCandidateSince = Date.MinValue

                ' Przy ciszy nie lecimy od razu na szeroki.
                ' Trzymamy ostatni close-up przez PAUSE_HOLD_MS.
                If (nowTime - silenceSince).TotalMilliseconds <= PAUSE_HOLD_MS Then
                    target = lastCloseShot
                Else
                    ' Po dłuższej ciszy można dać szeroki
                    target = WIDE_INPUT
                End If

            Else

                ' Niejednoznacznie -> preferuj ostatni close-up, nie szeroki
                leftCandidateSince = Date.MinValue
                rightCandidateSince = Date.MinValue
                wideCandidateSince = Date.MinValue
                silenceSince = Date.MinValue

                target = lastCloseShot

            End If
        End If
    End If

    If target <> currentShot Then
        If (nowTime - lastSwitchTime).TotalMilliseconds >= MIN_SHOT_MS Then
            API.Function(TRANSITION_NAME, Input:=target)
            currentShot = target
            lastSwitchTime = nowTime

            If target = LEFT_CLOSE_INPUT Or target = RIGHT_CLOSE_INPUT Then
                lastCloseShot = target
            End If
        End If
    End If

    Console.WriteLine("A_L=" & leftLevel.ToString("0.000") & " | A_R=" & busARightLevel.ToString("0.000") & " | B_L=" & busBLeftLevel.ToString("0.000") & " | RIGHT_USED=" & rightLevel.ToString("0.000") & " | LAST_CLOSE=" & lastCloseShot & " | CURRENT=" & currentShot)

    Sleep(LOOP_MS)

Loop

Jak działa ten skrypt?

  1. Skrypt analizuje poziomy audio z BUS A oraz BUS B i na tej podstawie wykrywa, która strona aktualnie mówi.
  2. BUS A Left steruje lewym close-upem, czyli inputem ustawionym w zmiennej LEFT_CLOSE_INPUT.
  3. BUS A Right lub BUS B Left steruje prawym close-upem, czyli inputem ustawionym w zmiennej RIGHT_CLOSE_INPUT.
  4. Jeżeli mówi jedna osoba i jej poziom dominuje nad drugą stroną, skrypt przełącza obraz na odpowiedni close-up.
  5. Jeżeli mówią obie strony albo po dłuższej ciszy nie ma aktywnego rozmówcy, skrypt może przełączyć obraz na plan szeroki.
  6. Zmienne czasowe, takie jak MIN_SHOT_MS, SPEAKER_HOLD_MS i PAUSE_HOLD_MS, chronią przed zbyt szybkim i nerwowym przełączaniem kamer.

Jak dodać skrypt w vMix?

  1. Wejdź w Settings → Scripting.
  2. Kliknij Add i wybierz skrypt VB.NET.
  3. Wklej kod z tej strony.
  4. Dostosuj inputy, BUS-y i progi działania bezpośrednio w kodzie.
  5. Uruchom skrypt i przetestuj na kopii projektu przed realizacją live.

Ważne: skrypt działa w pętli, więc zatrzymujesz go ręcznie w panelu Scripting.

Pomogło? Skrypty są darmowe. Jeśli chcesz docenić moją pracę, możesz postawić mi kawę BLIK-iem.

Odbiorca BLIK: Tomek Cedro
Tytuł: Kawa za skrypt vmix
+48 536 800 099
cedrotomasz@gmail.com