# -*- coding: UTF-8 -*-
"""! Video-Recording
capellaScript -- (c) 27.8.2026 Thomas Schneider (7718)
>>> Video-Recording

Versionsübersicht:
- Version 1.0, 27.8.2026 - 1. Release
- Version 1.1, 1.9.2026
  - Verbesserte Erkennung des Capella-Fensters

Autor: Thomas Schneider|
eMail: Thomas.Schneider@sys-thos.de|
web:   https://sys-thos.de/download/privat|
<<<
"""
## @file
# CapellaScript -- Thomas Schneider (7718)
## @package VideoRecording
#  @brief   Bildschirm-Recorder für Capella
#

import ctypes
import subprocess
import shlex
import os
#import time

# ==============================================================================
# Windows-spezifisch:
#if sys.platform == 'win32':
from pyKey_win import *

grabvideo = 'gdigrab'
grabaudio = 'dshow'

# ==============================================================================

def playScore():
    pressKey('CTRL')
    press('SPACEBAR')
    releaseKey('CTRL')

def showMP3Dlg(mp3name):
    # Datei-Menü öffnen
    pressKey('ALT')
    press(tr('menuFile'))
    releaseKey('ALT')

    # durchhangeln zum Klang- und Midi-Dialog:
    press(tr('menuExport'))
    press(tr('menuSound'))

    # MP3 auswählen
    pressKey('ALT')
    press(tr('menuMP3'))
    releaseKey('ALT')

    # Eingabe-Feld Sound-Dateiname auswählen:
    pressKey('ALT')
    press(tr('menuSoundFile'))
    releaseKey('ALT')

    # Dateiname ins Eingabefeld: funktioniert nicht?!
    #sendSequence(mp3name)

    # stattdessen per Copy/Paste:
    # Dateiname ins Clipboard kopieren...
    Clipboard.setText(mp3name)
    # ...und einfügen:
    # funktioniert leider auch nicht - aber immerhin kann man das dann selber drücken:
    #pressKey('CTRL')
    #press('v')
    #releaseKey('CTRL')

    # und starten:
    #press('ENTER')

#def undo(): # funktioniert leider nicht
#    pressKey('CTRL')
#    press('z')
#    releaseKey('CTRL')

# ------------------------------------------------------------------------------

german = {
    # Menü-Kürzel - Deutsch:
    'menuFile'          : 'd',
    'menuExport'        : 'e',
    'menuSound'         : 'k',
    'menuMP3'           : 'p',
    'menuSoundFile'     : 'd',
    # -------------------
    'selfuncTitle'      : 'Funktionsauswahl',
    'funcTitle'         : 'Funktionen',
    'ffmpeg'            : '&ffmpeg-Verzeichnis setzen',
    'video'             : '&Video-Aufnahme',
    'audio'             : '&Audio(MP3)-Dialog',
    'repeatDlg'         : 'Dialog erneut aufrufen: Änderungen anzeigen',
    'configureAudio'    : 'Audio(MP3) konfigurieren',
    'configureVideo'    : 'Video-Ausgabe konfigurieren',
    'outputFilePattern' : 'Ausgabe-Datei oder -Pattern (ohne Dateiendung):',
    'outputFile'        : 'Ausgabe-Datei:',
    'audioFile'         : 'Audio-Datei',
    'videoFile'         : 'Video-Datei',
    'recordStarted'     : 'Aufnahme gestartet',
    'ffmpeg_path'       : 'ffmpeg-Pfad:',
    'audioSource'       : 'Audio-Quelle = MP3-Datei:',
    'audioDevs'         : 'Audio-Devices:',
    'capellaWindow'     : 'Capella-Fenster = {winsize} - Offsets:',
    'left'              : 'links:',
    'right'             : 'rechts:',
    'top'               : 'oben:',
    'bottom'            : 'unten:',
    'recordArea'        : '(Aufnahmebereich: {winsize})',
    'videoScaling'      : 'Videoskalierung/Breite:',
    'videoFormat'       : '(Videoformat: {winsize})',
    'frameRate'         : 'Framerate (fps):',
    'recordTime'        : 'Aufnahmedauer:',
    'playDelay'         : 'Vorspiel-Delay(sec):',
    'playDelayHint'     : 'Vorspiel startet in {startDelay} Sekunden',
    'errCapNotFound'    : 'Capella-Fenster nicht gefunden / Capella-Datei wurde nicht gespeichert!\n',
    'errInitialisation' : 'Fehler bei der Initialisierung',
    'errAudioNotValid'  : 'Kein Audio-Gerät ausgewählt.\n',
    'errRecordTime'     : 'Aufnahmedauer nicht angegeben / Datei "{mp3file}" nicht gefunden!\n',
    'errAudioFile'      : 'Audio-Datei "{mp3file}" nicht gefunden!\n',
    'errConfDlg'        : 'Fehler bei der Konfiguration',
    '':''
}

english = {
    # Menü-Kürzel - Englisch:
    'menuFile'          : 'f',  # ???
    'menuExport'        : 'e',  # ???
    'menuSound'         : 's',  # ???
    'menuMP3'           : 'p',  # ???
    'menuSoundFile'     : 'f',  # ???
    # -------------------
    'selfuncTitle'      : 'Select function',
    'funcTitle'         : 'Functions',
    'ffmpeg'            : 'Select &ffmpeg directory',
    'video'             : '&Video recording',
    'audio'             : '&Audio(MP3) dialog',
    'repeatDlg'         : 'Repeat dialog: Show changes',
    'configureAudio'    : 'Configure Audio(MP3)',
    'configureVideo'    : 'Configure Video output',
    'outputFilePattern' : 'Output file or -pattern (without extension):',
    'outputFile'        : 'Output file:',
    'audioFile'         : 'Audio file',
    'videoFile'         : 'Video file',
    'recordStarted'     : 'Recording started',
    'ffmpeg_path'       : 'ffmpeg path:',
    'audioSource'       : 'Audio source = MP3 file:',
    'audioDevs'         : 'Audio devices:',
    'capellaWindow'     : 'Capella window = {winsize} - Offsets:',
    'left'              : 'left:',
    'right'             : 'right:',
    'top'               : 'top:',
    'bottom'            : 'bottom:',
    'recordArea'        : '(Record area: {winsize})',
    'videoScaling'      : 'Video scaling/width:',
    'videoFormat'       : '(Video format: {winsize})',
    'frameRate'         : 'Framerate (fps):',
    'recordTime'        : 'Record time:',
    'playDelay'         : 'Start delay(sec):',
    'playDelayHint'     : 'Playing starts in {startDelay} seconds',
    'errCapNotFound'    : 'Capella window not found / Capella file not saved!\n',
    'errInitialisation' : 'Initialisation error',
    'errAudioNotValid'  : 'Audio device not selected.\n',
    'errRecordTime'     : 'Record time not set / file "{mp3file}" not found!\n',
    'errAudioFile'      : 'Audio file "{mp3file}" not found!\n',
    'errConfDlg'        : 'Configuration error',
    'capellaWindow'     : 'Capella window = {winsize} - Offsets:',
    '':''
}

try:
    setStringTable(
        ("en", english),
        ("de", german)
    )
except:
    def tr(s):
        return german[1].get(key, key)

def tr2(key, **kwargs):
    text = tr(key)
    # Platzhalter ersetzen wenn vorhanden
    if kwargs:
        try:
            text = text.format(**kwargs)
        except:
            pass
    return text

# ------------------------------------------------------------------------------

# Img-Symbole für messageBox():
dlgImgNone      = 0 # = keines (Standard)
dlgImgQuestion  = 1 # = Fragezeichen
dlgImgInfo      = 2 # = Info
dlgImgWarning   = 3 # = Ausrufezeichen
dlgImgStop      = 4 # = Stop
# Buttons für messageBox():
btnOK           = 0 # = OK (Standard)
btnOkCancel     = 1 # = OK/Abbrechen
btnYesNO        = 2 # = Ja/Nein
btnYesNoCancel  = 3 # = Ja/Nein/Abbrechen

# ------------------------------------------------------------------------------
# Logging
# ------------------------------------------------------------------------------
pyFile = sys.argv[0]
pyDir, pyName = os.path.split(pyFile)
logFile = os.path.splitext(pyFile)[0] + '.log'

# Logdatei neu erzeugen
def initLog():
    try:
        with open(logFile,"w"):
            pass
    except:
        pass

# Log-Ausgabe
def doLog(logInfo,logString):
    try:
        with open(logFile,"a") as f:
            f.write("\n"+logInfo+"\n")
            f.write(logString)
    except:
        pass

# Logging initialisieren
initLog()

# ------------------------------------------------------------------------------
## @brief Basisklasse für Script-Optionen
class Options(ScriptOptions):
    """!
    Basisklasse für Script-Optionen:
    Die Klasse stellt Methoden zum Lesen und Schreiben von Optionen
    unterschiedlicher Datentypen zur Verfügung.

    Die Methode _getOptions() sollte zuerst aufgerufen werden; damit ist
    sichergestellt, dass das lokale Dictionary @ref self.opt existiert und gültige Daten enthält.
    """
    def __init__(self):
        ScriptOptions.__init__(self)
        self._getOptions()

    def _getOptions(self):
        """! liest alle Optionen aus der Standard-Option-Datei in das interne Dictionary @ref opt """
        self.opt = self.get()
    def _setOptions(self):
        """! schreibt das interne Dictionary @ref opt in die Option-Datei """
        self.set(self.opt)

    ## Bool'sche Werte lesen
    def _getAsBool(self, key, default = False):
        # mit bool scheint es Konsistenz-Probleme beim Schreiben und Lesen zu geben?!
        # Deshalb hier etwas umständlicher:
        try:
            return False if not self.opt[key] or self.opt[key] in ("False","0") else True
        except:
            return default
    ## Bool'sche Werte schreiben
    def _setAsBool(self, key, value):
        self.opt[key] = bool(value)

    def _getAsInt(self, key, default = 0):
        return int(self.opt.get(key,default))
    def _setAsInt(self, key, value):
        self.opt[key] = int(value)

    def _getAsFloat(self, key, default = 0.0):
        return float(self.opt.get(key,default))
    def _setAsFloat(self, key, value):
        self.opt[key] = float(value)

    def _getAsStr(self, key, default = ""):
       return str(self.opt.get(key,default))
    def _setAsStr(self, key, value):
        self.opt[key] = str(value)

# ------------------------------------------------------------------------------
## Verwaltung der Script-Optionen

# ------------------------------------------------------------------------------
class AppScriptOptions(Options):

    def __init__(self):
        Options.__init__(self)

        # Variablen-Defaults setzen
        self.ffmpeg_exe = "ffmpeg.exe"
        self.ffmpeg_path = None

        self.audio_device = ''

        self.offsLeft   = 20
        self.offsTop    = 100
        self.offsRight  = 36
        self.offsBottom = 27

        self.video_width   = 1080
        self.frame_rate    = "15"

        self.startDelay = 5

        self.OutputFile = '{SCOREDIR}/{SCORENAME}_{VOICE}'   ##< Name der Ausgabe-Datei oder Namensmuster

        self.Update()

    # Dictionary in die lokalen Variablen einlesen
    def Update(self):
        self.ffmpeg_path  = self._getAsStr('ffmpeg_path', self.ffmpeg_path)
        self.audio_device = self._getAsStr('audio_device',self.audio_device)

        self.offsLeft   = self._getAsInt('offsLeft',  self.offsLeft)
        self.offsTop    = self._getAsInt('offsTop',   self.offsTop)
        self.offsRight  = self._getAsInt('offsRight', self.offsRight)
        self.offsBottom = self._getAsInt('offsBottom',self.offsBottom)

        self.video_width = self._getAsInt('video_width',self.video_width)
        self.frame_rate  = self._getAsStr('frame_rate',self.frame_rate)

        self.startDelay  = self._getAsInt('startDelay',self.startDelay)

        self.OutputFile = self._getAsStr('OutputFile',self.OutputFile)

    def Save(self):
        self._setAsStr('ffmpeg_path',  self.ffmpeg_path)
        self._setAsStr('audio_device', self.audio_device)

        self._setAsInt('offsLeft',   self.offsLeft)
        self._setAsInt('offsTop',    self.offsTop)
        self._setAsInt('offsRight',  self.offsRight)
        self._setAsInt('offsBottom', self.offsBottom)

        self._setAsInt('video_width',self.video_width)
        self._setAsStr('frame_rate',self.frame_rate)

        self._setAsInt('startDelay',self.startDelay)

        self._setAsStr('OutputFile',self.OutputFile)

        self._setOptions()

    ## @brief korrigiert \p windowsize um die Offsets
    #
    #  @result X,Y Breite / Höhe als integer
    def video_range(self, windowsize):
        sizeX, sizeY = windowsize.split('x',1)
        X = int(sizeX)-self.offsLeft-self.offsRight
        Y = int(sizeY)-self.offsTop-self.offsBottom
        return (X, Y)

    ## @brief Aufnahme-Bereich als String "BREITExHÖHE"
    def video_size(self, windowsize):
        X, Y = self.video_range(windowsize)
        return str(X)+"x"+str(Y)

    ## @brief Ausgabe-Format als String "BREITExHÖHE"
    def video_format(self, windowsize):
        if self.video_width == "": return video_size()
        X, Y = self.video_range(windowsize)
        video_height = self.video_width*Y/X
        return str(self.video_width)+"x"+str(video_height)

    # --------------------------------------------------------------------------
    ## @brief ffmpeg-Pfad konfigurieren
    def ffmpeg_Config(self, force=False):
        if force or (self.ffmpeg_path == None) or   \
           not os.path.exists(os.path.join(self.ffmpeg_path,self.ffmpeg_exe)):

            dlg = FileDialog(True)          # nur existierende Dateien zulässig!
            dlg.setTitle('ffmpeg_Config')
            dlg.addFilter('exe', '*.exe')
            dlg.addFilter('alle Dateien', '*.*')
            dlg.setStartPath(self.ffmpeg_path)
            dlg.setStartFile(self.ffmpeg_exe)

            if not dlg.run(): return False

            path,name = os.path.split(dlg.filePath())
            if name != self.ffmpeg_exe: return False

            self.ffmpeg_path = path

        os.chdir(self.ffmpeg_path)
        return True

# ------------------------------------------------------------------------------
## @brief Verwaltung von Optionen im info/comment-Bereich der Capella-Partitur
#
#  Die Klasse unterstützt Option-Blöcke mit Schlüssel/Werte-Paaren im info/comment-Bereich:
#  <Block-Anfang>   beliebige Zeichenkette
#  <key>:<value>    Option-Einträge
#  ...
#  <Block-Ende>     beliebige Zeichenkette
class ScoreOptions():

    def __init__(self):
        self.getInfoComment()

    ## @brief info/comment aus der Capella-Partitur auslesen
    def getInfoComment(self):
        root = rootNode()
        info = root.getChildNode("info", 0)
        self.infoComment = info.getChildNode("comment", 0).getText()

    ## @brief Zeilen zwischen \p start und \p end aus info/comment auslesen
    #
    #  @param start Beginn des Blocks
    #  @param end   Ende des Blocks
    #  @result Zeilen zwischen \p start und \p end (exclusive!)
    def getInfoLines(self,start,end):
        lines = self.infoComment.splitlines()
        try:
            i = lines.index(start)+1
            j = lines[i:].index(end)+i
            if i < j: return lines[i:j]
        except:
            pass
        return ""

    ## @brief liefert den Schlüsselwert aus dem Block \p lines mit dem Schlüssel \p key
    #
    # @result Wert des Schlüssel \p key oder "", falls \p key nicht vorhanden
    def getValue(self,lines,key,sep=':'):
        for line in lines:
            try:
                Key,Value = line.split(sep,1)
                if Key == key:
                    return Value.strip()
            except:
                pass

        return ""

    ## @brief liefert den Schlüsselwert aus dem info/comment-Bereich-Block \p start ... \p end mit dem Schlüssel \p key
    #
    # @result Wert des Schlüssel \p key oder "", falls \p key nicht vorhanden
    def getInfoValue(self,start,end,key,sep=':'):
        return self.getValue(self.getInfoLines(start,end),key,sep)

# ------------------------------------------------------------------------------
## @brief Klasse zum Auslesen des info/comment-Bereichs mit den Daten des Scripts \p video-preparation
class AppScoreOptions(ScoreOptions):
    #duration = "10"
    #voice    = ""
    def __init__(self):
        ScoreOptions.__init__(self)
        #self.volumes  = self.getVideoPrep('volumes')   # wird hier nicht gebraucht
        self.voice    = self.getVideoPrep('colored')
        self.duration = self.getVideoPrep('duration')   # könnte im Script "video-preparation" ergänzt werden

    def getVideoPrep(self,key):
        return self.getInfoValue('#VIDEO-PREP-DATA#','#END#',key)

# ------------------------------------------------------------------------------
# globale Daten
appScriptOptions = AppScriptOptions()
appScoreOptions  = AppScoreOptions()

class AppOptions():
    def __init__(self,grabvideo,grabaudio):
        self.grabvideo = grabvideo
        self.grabaudio = grabaudio
        self.audio_devices = ['']
        self.windowtitle = ''
        self.windowsize  = ''
        self.mp3audio    = ''
        self.scoredir    = ''
        self.scorefile   = ''
        self.scorename   = ''

    def setScorePath(self):
        self.scoredir,self.scorefile = os.path.split(activeScore().pathName())
        self.scorename = os.path.splitext(self.scorefile)[0]

    def indexAudioDevice(self):
        try:
            n = self.audio_devices.index(appScriptOptions.audio_device)
        except:
            n = 0
        return n

appOptions = AppOptions(grabvideo,grabaudio)

# ------------------------------------------------------------------------------
# globale Funktionen
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
## @brief Hilfsfunktion: Verzeichnis-Pfad rekursiv erzeugen
def makeDir(filename):
    dirname = os.path.split(filename)[0]
    if not os.path.exists(dirname):
        os.makedirs(dirname)

# ------------------------------------------------------------------------------
## @brief Dummy-Label für Dialoge: Abstand zwischen 2 Dialog-Elementen mit \p n Leerzeichen
def padLabel(n):
    return Label(n*' ')

# ------------------------------------------------------------------------------
## @brief Name der Ausgabe-Datei
#
#  Die Funktion ersetzt die folgenden Platzhalter in \p outfile:
#  - {SCOREDIR}:  absolutes Verzeichnis der Capella-Datei
#  - {SCORENAME}: Name der Capella-Datei ohne Dateiendung \p .capx
#  - {VOICE}:     hervorgehobene Stimme \p appScoreOptions.voice
#
#  Wird kein Dateiname übergeben, so wird stattdessen der Dateiname bzw. das
#  Namensmuster aus \p appScriptOptions.OutputFile verwendet.
#  Ist der Dateiname bzw. das Namensmuster relativ (d.h. beginnt mit einem Punkt),
#  dann wird der Dateiname relativ zum Verzeichnis der Capella-Datei interpretiert.
#
#  @result absoluter Pfad zu \p outfile bzw. \p appScriptOptions.OutputFile
def getOutputFile(outfile=None):
    if outfile == None:
        outfile = appScriptOptions.OutputFile
    if outfile[0] == '.':
        outfile = os.path.normpath(appOptions.scoredir+'/'+outfile)
    return outfile.replace('{SCOREDIR}',appOptions.scoredir)    \
                  .replace('{SCORENAME}',appOptions.scorename)  \
                  .replace('{VOICE}',appScoreOptions.voice)     \
                  .replace('\\','/')

# ------------------------------------------------------------------------------
## @brief Name der Ausgabe-Datei
#
#  Die Funktion ersetzt die folgenden Platzhalter in \p outfile:
#  - {SCOREDIR}:  absolutes Verzeichnis der Capella-Datei
#  - {SCORENAME}: Name der Capella-Datei ohne Dateiendung \p .capx
#  - {VOICE}:     hervorgehobene Stimme \p appScoreOptions.voice
#
#  Wird kein Dateiname übergeben, so wird stattdessen der Dateiname bzw. das
#  Namensmuster aus \p appScriptOptions.OutputFile verwendet.
#  Ist der Dateiname bzw. das Namensmuster relativ (d.h. beginnt mit einem Punkt),
#  dann wird der Dateiname relativ zum Verzeichnis der Capella-Datei interpretiert.
#
#  @result relativer Pfad zu \p outfile bzw. \p appScriptOptions.OutputFile
def getOutputFileRel(outfile=None):
    outfile = getOutputFile(outfile)
    if not outfile.startswith(appOptions.scoredir): return outfile
    return './' + os.path.relpath(outfile, appOptions.scoredir)

# ------------------------------------------------------------------------------
## @brief Liefert die Aufzeichnungsdauer incl. des Start-Delays
#
# @result Aufzeichnungsdauer in Sekunden
def getRecordTime():
    try:
        d = appScoreOptions.duration.split(":")
    except:
        d = appScoreOptions.duration

    dd = 0
    #if len(d) > 3: Fehler
    for t in d:
        dd = dd*60 + float(t)
    #messageBox('',str(dd))

    return str(dd+appScriptOptions.startDelay)

# ------------------------------------------------------------------------------
## @brief Name des Capella-Fensters
#
#  Fenster-Titel = Dateiname ergänzt um die Capella-Version
#
#  Wurde die Partitur noch nicht gespeichert, dann steht ein zusätzliches "*"
#  im Fenster-Titel - leider kann man das nicht abfragen! Per Default wird davon
#  ausgegangen, dass die Partitur gespeichert wurde, d.h. kein "*" bzw. \p ChgID = "".
#  Alternativ kann die Funktion auch mit \p isChg = True aufgerufen werden und
#  liefert dann den Fenster-Namen für die nicht gespeicherte Partitur.
def scoreWindowTitle(isChg = False, isDemo = False):
    # return activeScore().title: schön wars, wenn hier der komplette Fenstertitel geliefert würde...

    filename = activeScore().pathName();
    title = os.path.split(filename)[1]

    ver, sub, sr = capVersion()
    ChgID = "" if not isChg  else "*"
    Demo  = "" if not isDemo else " (Demo)"
    return title + " "+ChgID+" - capella " + str(ver) + Demo

# ------------------------------------------------------------------------------
## @brief Fenster-Größe, Audio-Devices und Aufnahme-Dauer ermitteln
#
#  Die Daten werden durch einen (Dummy-)Aufruf von ffmpeg ermittelt; die
#  Rückgabe des ffmpeg-Aufrufs enthält die gewünschten Daten, die anschließend
#  entsprechend geparst werden:
#
#  Informationen zur mp3-Datei, falls vorhanden, z.B.
#  - Input #0, mp3, from '.../Partitur 1.mp3':
#    - Duration: 00:00:12.54, start: 0.025057, bitrate: 191 kb/s
#
#  Informationen zum Capella-Fenster, z.B.:
#  - [gdigrab @ ...] Found window Partitur 1.capx * - capella 8, capturing 1920x1057x32 at (0,0)\n
#    bzw.\n
#    [gdigrab @ ...] Can't find window 'Partitur 1.capx  - capella 8', aborting.
#  - Duration: N/A, start: 1787257133.172213, bitrate: 1946329 kb/s
#
#  verfügbare Audio-Devices, z.B.:
#  - [dshow @ ...] "Microphone Array (AMD Audio Device)" (audio)
#  - [dshow @ ...] "Stereomix (Realtek(R) Audio)" (audio)
#
#  und eine Fehlermeldung wg. "-f dshow -i ''"!
#  - Error opening input file
#
#  @attention
#  ffmpeg schreibt *immer* nach stderr, nicht nach stdout!
#
#  Die Ergebnisse werden in die entsprechenden Variablen eingetragen.
def ffmpeg_Initialize(mp3name):

    def logResult(out,err):
        doLog("ffmpeg_Initialize - stdout:", out)
        doLog("ffmpeg_Initialize - stderr:", err)

    def ffmpegSearch(windowname,mp3input):
        ffmpeg_cmd = appScriptOptions.ffmpeg_exe    \
                   + " -hide_banner -nostdin"       \
                   + mp3input                       \
                   + " -f "+appOptions.grabvideo+" -i title='"+windowname    \
                   + "' -list_devices true -f "+appOptions.grabaudio+" -i ''"

        si = subprocess.STARTUPINFO()
        si.dwFlags = subprocess.STARTF_USESHOWWINDOW
        si.wShowWindow = 0  # = SW_HIDE --> beschleunigt die Ausführung von popen.communicate()!

        process = subprocess.Popen(shlex.split(ffmpeg_cmd), universal_newlines=True,
                                   stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=si)
        out, err = process.communicate()

        logResult(out,err)

        if err.find("Can't find window '"+appOptions.windowtitle+"', aborting.") >= 0:
            return ""

        return out+'\n'+err     # sicherheitshalber beides
    # --------------------------------------------------------------------------

    if os.path.exists(mp3name):
        mp3input = " -i '"+mp3name+"'"
    else:
        mp3input = ""

    # Capella-Fenster suchen: zuerst ohne "*", dann ggf. mit "*" im Fensternamen
    appOptions.windowtitle = scoreWindowTitle()
    ffmpegResult = ffmpegSearch(appOptions.windowtitle,mp3input)
    if ffmpegResult == "":
        appOptions.windowtitle = scoreWindowTitle(True)
        ffmpegResult = ffmpegSearch(appOptions.windowtitle,mp3input)
    if ffmpegResult == "":
        appOptions.windowtitle = scoreWindowTitle(False,True)
        ffmpegResult = ffmpegSearch(appOptions.windowtitle,mp3input)
    if ffmpegResult == "":
        appOptions.windowtitle = scoreWindowTitle(True,True)
        ffmpegResult = ffmpegSearch(appOptions.windowtitle,mp3input)
    if ffmpegResult == "":
        appOptions.windowtitle = ""
        appOptions.windowsize = ""
        appScoreOptions.duration = ""
        return

    lines = ffmpegResult.splitlines()

    # lines parsen:
    text = appOptions.windowtitle+"\n";
    appOptions.audio_devices = ['']
    winstr = appOptions.windowtitle+', capturing '  # Suchstring = "<window name>, capturing " (incl. " " am Ende!)
    durstr = '  Duration: '                         # Suchstring für die Abspieldauer der mp3-Datei
    for line in lines:
        p = line.find(winstr)
        if p >= 0:                      # line = [gdigrab @ ...] Found window <winstr><sizeX>x<sizeY>x32 at (0,0)
            appOptions.windowsize = line[-(len(line)-p-len(winstr)):]  # vorderen Teil abtrennen --> <sizeX>x<sizeY>x32 at (0,0)
            sizeX, sizeY, dummy = appOptions.windowsize.split('x',2)   # an den beiden 'x' splitten
            appOptions.windowsize = sizeX +"x"+ sizeY
            text += line + "\n" + appOptions.windowsize + "\n"
        elif line.find('(audio)') >= 0: # line = [dshow @ ...] "<audio device name>" (audio)
            dev = line.split('"',2)[1]  # an den beiden '"' splitten: dazwischen = <audio device name>
            appOptions.audio_devices.append(dev)
            text += line + "\n" + dev +"\n"
        elif line.find(durstr) >= 0:
            d = line[len(durstr):]  # vorderen Teil abtrennen
            d = d[:d.find(',')]     # alles bis zum Komma
            if d <> 'N/A':
                appScoreOptions.duration = d

    #messageBox('ffmpeg_Initialize',text)

# ------------------------------------------------------------------------------
## @brief Konfiguration der Video-Aufnahme
#
#
def configureVideo():

    def videoDlg():

        relBaseFileName = getOutputFileRel()

        lbl_ffmpeg  = Label(tr('ffmpeg_path')+'\n'+appScriptOptions.ffmpeg_path+'\n',width=55)
        cbMP3File  = CheckBox(tr('audioSource'), value=appOptions.mp3audio <> "")
        lblMP3file = Label(relBaseFileName+'.mp3 \n')

        lblDevs = Label(tr('audioDevs'),width=55)
        cbDevs = ComboBox(appOptions.audio_devices,width=30,value = appOptions.indexAudioDevice())
        #devBox = HBox([lblDevs,cbDevs])

        audioBox = VBox([cbMP3File,lblMP3file,lblDevs,cbDevs],text='Audio-Konfiguration',padding=5)

        # --------------------------------------------------------------------------
        lblWinSize = Label(tr2('capellaWindow',winsize=appOptions.windowsize),width=55)

        lblOffsLeft     = Label(tr('left'),   width=10)
        lblOffsTop      = Label(tr('top'),    width=10)
        lblOffsRight    = Label(tr('right'),  width=10)
        lblOffsBottom   = Label(tr('bottom'), width=10)
        lblOffsBox      = HBox([lblOffsLeft,lblOffsTop,lblOffsRight,lblOffsBottom],padding=5);

        edtOffsLeft     = Edit(str(appScriptOptions.offsLeft),   min=0, width=10)
        edtOffsTop      = Edit(str(appScriptOptions.offsTop),    min=0, width=10)
        edtOffsRight    = Edit(str(appScriptOptions.offsRight),  min=0, width=10)
        edtOffsBottom   = Edit(str(appScriptOptions.offsBottom), min=0, width=10)
        edtOffsBox      = HBox([edtOffsLeft,edtOffsTop,edtOffsRight,edtOffsBottom],padding=5);

        lblRecSize = Label(tr2('recordArea',winsize=appScriptOptions.video_size(appOptions.windowsize)),width=55)

        winBox = VBox([lblWinSize,
                       lblOffsBox,
                       edtOffsBox,
                       lblRecSize
                      ], text = 'Video-Fenster', padding=5)

        # --------------------------------------------------------------------------
        lblVideoWidth = Label(tr('videoScaling'),width=20)
        edtVideoWidth = Edit(str(appScriptOptions.video_width), min=0)
        lblVideoFormat = Label(tr2('videoFormat',winsize=appScriptOptions.video_format(appOptions.windowsize)),width=26)
        vwBox         = HBox([lblVideoWidth,edtVideoWidth,padLabel(2),lblVideoFormat])

        lblFramerate = Label(tr('frameRate'),width=20)
        edtFramerate = Edit(appScriptOptions.frame_rate, min=0)
        fpsBox       = HBox([lblFramerate,edtFramerate])

        lblDuration = Label(tr('recordTime'),width=15)
        edtDuration = Edit(appScoreOptions.duration, width=10)
        lblDelay    = Label(tr('playDelay'),width=15)
        edtDelay    = Edit(str(appScriptOptions.startDelay), min=0)
        durationBox = HBox([lblDuration,edtDuration,padLabel(2),lblDelay,edtDelay])

        recordBox   = VBox([vwBox,fpsBox,durationBox],text='Aufnahme-Parameter')

        # --------------------------------------------------------------------------

        lblOutputFile = Label(tr('outputFilePattern'),width=55)
        edtOutputFile = Edit(appScriptOptions.OutputFile, width=50)
        outputFileBox = VBox([lblOutputFile,edtOutputFile,Label(relBaseFileName+'.mp4 ')],text=tr('videoFile'), padding=5)

        # --------------------------------------------------------------------------
        cbRepeat      = CheckBox(tr('repeatDlg'), value=False)
        dlgBox = VBox([Label('',width=60),
                       lbl_ffmpeg,
                       outputFileBox,
                       audioBox,
                       winBox,
                       recordBox,
                       cbRepeat
                      ])

        dlg = Dialog(tr('configureVideo'), dlgBox)
        if not dlg.run(): return False

        appScriptOptions.audio_device = appOptions.audio_devices[cbDevs.value()]
        appOptions.mp3audio = getOutputFileRel()+'.mp3' if cbMP3File.value() else ''

        appScriptOptions.offsLeft     = int(edtOffsLeft.value())
        appScriptOptions.offsRight    = int(edtOffsRight.value())
        appScriptOptions.offsTop      = int(edtOffsTop.value())
        appScriptOptions.offsBottom   = int(edtOffsBottom.value())

        appScriptOptions.video_width  = int(edtVideoWidth.value())
        appScriptOptions.frame_rate   = edtFramerate.value()

        appScriptOptions.OutputFile   = edtOutputFile.value()

        appScriptOptions.startDelay   = int(edtDelay.value())
        appScoreOptions.duration      = edtDuration.value()

        return None if cbRepeat.value() else True

    #---------------------------------------------------------------------------
    result = None
    while result == None:
        result = videoDlg()

    return result

# ------------------------------------------------------------------------------
def VideoRecording():

    filename = activeScore().pathName();
    mp3name = os.path.splitext(filename)[0] + '.mp3'
    #mp4name = os.path.splitext(filename)[0] + '.mp4'

    # Pfad zu ffmpeg initialisieren:
    if not appScriptOptions.ffmpeg_Config():
        return False

    # Capella-Fenster + Audio-Devices lokalisieren und Aufnahmedauer bestimmen
    ffmpeg_Initialize(mp3name)

    errmsg = ""
    if (appOptions.windowsize == "") or (appOptions.windowtitle == ""):
        errmsg += tr('errCapNotFound')
    if errmsg != "":
        messageBox(tr('errInitialisation'),errmsg,dlgImgStop,btnOK)
        return False

    # --------------------------------------------------------------------------
    if configureVideo():

        errmsg = ""
        if appScriptOptions.audio_device == "":
            errmsg += tr('errAudioNotValid')
        if appScoreOptions.duration      == "":
            errmsg += tr2('errRecordTime',mp3file=os.path.split(mp3name)[1])
        if (appOptions.mp3audio <> "") and not os.path.exists(appOptions.mp3audio):
            errmsg += tr2('errAudioFile',appOptions.mp3audio)
        if errmsg != "":
            messageBox(tr('errConfDlg'),errmsg,dlgImgStop,btnOK)
            return False

        # ----------------------------------------------------------------------
        # Cursor auf den Beginn der Partitur:
        setSelection(((0,0,0,0),(0,0,0,0)))

        windowsetting = " -offset_x "+str(appScriptOptions.offsLeft)    \
                      + " -offset_y "+str(appScriptOptions.offsTop)     \
                      + " -video_size "+appScriptOptions.video_size(appOptions.windowsize)

        if appOptions.mp3audio == "":
            audio = " -f "+appOptions.grabaudio+" -i audio='"+appScriptOptions.audio_device+"'"
        else:
            audio = " -itsoffset "+str(appScriptOptions.startDelay)+" -i '"+appOptions.mp3audio+"'"

        video = " -f "+appOptions.grabvideo + windowsetting + " -framerate "+appScriptOptions.frame_rate+" -draw_mouse 0 -i title='"+appOptions.windowtitle+"'"

        duration = " -t "+getRecordTime()

        mp4name = getOutputFile()+'.mp4'
        makeDir(mp4name)

        ffmpeg_cmd = appScriptOptions.ffmpeg_exe+" -hide_banner"        \
                   + video+audio                                        \
                   + " -s " +appScriptOptions.video_format(appOptions.windowsize)  \
                   + duration+" -y '"+mp4name+"'"
        doLog('VideoRecording - ffmpeg_cmd:',ffmpeg_cmd+"\n\n")

        global logFile
        cmd = shlex.split(ffmpeg_cmd + " 2>> "+logFile)

        process = subprocess.Popen(cmd, shell=True)

        # Wartezeit nach dem Start von ffmpeg:
        #time.sleep(appScriptOptions.startDelay)    Wartezeit:  so gehts nicht: Popen wird ebenfalls verzögert,
        #playScore()                                und Score wird nicht abgespielt

        # Mit Dialog+timeout gehts! allerdings nur mit shell=True
        dlg = Dialog(tr('recordStarted'),
                      HBox([Label(tr('outputFile')+'\n' \
                                 +mp4name+'\n'
                                 +tr2('playDelayHint',startDelay=str(appScriptOptions.startDelay))),
                                 padLabel(2)], padding=5),
                      timeout=appScriptOptions.startDelay*1000)
        #if dlg.run() or (dlg['timeout'] == 0):
        #    playScore()    #startet u.U. nicht
        #else:
        #    process.terminate() #funktioniert nicht!
        dlg.run()
        playScore()

    # --------------------------------------------------------------------------
    return True

# ------------------------------------------------------------------------------
def AudioRecording():

    def audioDlg():

        # --------------------------------------------------------------------------

        relBaseFileName = getOutputFileRel()

        lblOutputFile = Label(tr('outputFilePattern'),width=55)
        edtOutputFile = Edit(appScriptOptions.OutputFile, width=50)
        lblMP3file = Label(relBaseFileName+'.mp3 \n')
        outputFileBox = VBox([lblOutputFile,edtOutputFile,lblMP3file],text=tr('audioFile'), padding=5)

        # --------------------------------------------------------------------------
        cbRepeat      = CheckBox(tr('repeatDlg'), value=False)
        dlgBox = VBox([Label('',width=60),
                       outputFileBox,
                       cbRepeat
                      ])

        dlg = Dialog(tr('configureAudio'), dlgBox)
        if not dlg.run(): return False

        appScriptOptions.OutputFile   = edtOutputFile.value()

        return None if cbRepeat.value() else True

    # --------------------------------------------------------------------------

    result = None
    while result == None:
        result = audioDlg()

    if result:
        showMP3Dlg(getOutputFile())

    return result

# ------------------------------------------------------------------------------

def selectRecordMode():

    appOptions.setScorePath()

    rbSelect = Radio([tr('ffmpeg'),tr('video'),tr('audio')],text=tr('funcTitle'),value=1)
    result = Dialog(tr('selfuncTitle'),HBox([rbSelect])).run()

    if result:
        if   rbSelect.value() == 1:
            result = VideoRecording()
        elif rbSelect.value() == 2:
            result = AudioRecording()
        else:   # rbSelect.value() == 0 oder nichts ausgewählt
            result = appScriptOptions.ffmpeg_Config(True)

    if result: appScriptOptions.Save()

# ------------------------------------------------------------------------------

if __name__ == '__main__':
    if activeScore():
        waitCtrlR_Released()

        selectRecordMode()
