Videre
This commit is contained in:
161
LineDancePlayer.spec
Normal file
161
LineDancePlayer.spec
Normal file
@@ -0,0 +1,161 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
#
|
||||
# LineDancePlayer.spec
|
||||
#
|
||||
# Byg med: pyinstaller LineDancePlayer.spec
|
||||
# Output: dist\LineDancePlayer.exe
|
||||
#
|
||||
# Kræver: VLC installeret på byggemaskinen
|
||||
# (typisk C:\Program Files\VideoLAN\VLC)
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# ── Find VLC-installation ─────────────────────────────────────────────────────
|
||||
|
||||
def find_vlc_path() -> Path | None:
|
||||
"""Find VLC på Windows — tjekker de mest almindelige installationsstier."""
|
||||
candidates = [
|
||||
Path(os.environ.get("PROGRAMFILES", "C:/Program Files")) / "VideoLAN" / "VLC",
|
||||
Path(os.environ.get("PROGRAMFILES(X86)", "C:/Program Files (x86)")) / "VideoLAN" / "VLC",
|
||||
Path("C:/Program Files/VideoLAN/VLC"),
|
||||
Path("C:/Program Files (x86)/VideoLAN/VLC"),
|
||||
]
|
||||
# Tjek også PYTHONPATH og registry via python-vlc
|
||||
try:
|
||||
import vlc
|
||||
vlc_path = Path(vlc.plugin_path).parent if vlc.plugin_path else None
|
||||
if vlc_path and vlc_path.exists():
|
||||
candidates.insert(0, vlc_path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for path in candidates:
|
||||
if path.exists() and (path / "libvlc.dll").exists():
|
||||
return path
|
||||
return None
|
||||
|
||||
|
||||
VLC_PATH = find_vlc_path()
|
||||
if VLC_PATH is None:
|
||||
print("=" * 60)
|
||||
print("ADVARSEL: VLC ikke fundet!")
|
||||
print("Installer VLC fra https://www.videolan.org/vlc/")
|
||||
print("og kør pyinstaller igen.")
|
||||
print("=" * 60)
|
||||
VLC_PATH = Path("C:/Program Files/VideoLAN/VLC") # fallback
|
||||
|
||||
print(f"VLC fundet: {VLC_PATH}")
|
||||
|
||||
# ── Saml VLC binære filer ─────────────────────────────────────────────────────
|
||||
|
||||
vlc_binaries = []
|
||||
vlc_datas = []
|
||||
|
||||
if VLC_PATH.exists():
|
||||
# Hoved-DLL filer
|
||||
for dll in ["libvlc.dll", "libvlccore.dll", "libvlc.lib"]:
|
||||
dll_path = VLC_PATH / dll
|
||||
if dll_path.exists():
|
||||
vlc_binaries.append((str(dll_path), "."))
|
||||
|
||||
# Plugins-mappe — indeholder codecs, demuxers osv.
|
||||
plugins_dir = VLC_PATH / "plugins"
|
||||
if plugins_dir.exists():
|
||||
vlc_datas.append((str(plugins_dir), "plugins"))
|
||||
|
||||
# Locale-filer
|
||||
locale_dir = VLC_PATH / "locale"
|
||||
if locale_dir.exists():
|
||||
vlc_datas.append((str(locale_dir), "locale"))
|
||||
|
||||
# ── PyInstaller konfiguration ─────────────────────────────────────────────────
|
||||
|
||||
block_cipher = None
|
||||
|
||||
a = Analysis(
|
||||
["main.py"],
|
||||
pathex=["."],
|
||||
binaries=vlc_binaries,
|
||||
datas=[
|
||||
("ui", "ui"),
|
||||
("local", "local"),
|
||||
("player", "player"),
|
||||
] + vlc_datas,
|
||||
hiddenimports=[
|
||||
# PyQt6
|
||||
"PyQt6.sip",
|
||||
"PyQt6.QtCore",
|
||||
"PyQt6.QtGui",
|
||||
"PyQt6.QtWidgets",
|
||||
# Lyd og tags
|
||||
"vlc",
|
||||
"mutagen",
|
||||
"mutagen.mp3",
|
||||
"mutagen.id3",
|
||||
"mutagen.flac",
|
||||
"mutagen.mp4",
|
||||
"mutagen.oggvorbis",
|
||||
"mutagen.oggopus",
|
||||
# Fil-overvågning
|
||||
"watchdog",
|
||||
"watchdog.observers",
|
||||
"watchdog.observers.polling",
|
||||
"watchdog.events",
|
||||
# Database
|
||||
"sqlite3",
|
||||
# Standard
|
||||
"json",
|
||||
"pathlib",
|
||||
"threading",
|
||||
"urllib.request",
|
||||
"urllib.parse",
|
||||
],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[
|
||||
# Ting vi ikke bruger — reducerer filstørrelse
|
||||
"tkinter",
|
||||
"matplotlib",
|
||||
"numpy",
|
||||
"pandas",
|
||||
"scipy",
|
||||
"PIL",
|
||||
"cv2",
|
||||
"pytest",
|
||||
],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name="LineDancePlayer",
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True, # komprimer med UPX hvis tilgængeligt
|
||||
upx_exclude=[
|
||||
"libvlc.dll", # VLC må ikke komprimeres — den loader plugins dynamisk
|
||||
"libvlccore.dll",
|
||||
],
|
||||
runtime_tmpdir=None,
|
||||
console=False, # ingen konsol-vindue
|
||||
disable_windowed_traceback=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
# Ikon — kommenter ud hvis du ikke har en .ico fil endnu
|
||||
# icon="assets/icon.ico",
|
||||
)
|
||||
68
build.bat
Normal file
68
build.bat
Normal file
@@ -0,0 +1,68 @@
|
||||
@echo off
|
||||
echo ================================================
|
||||
echo LineDance Player - Byg EXE
|
||||
echo ================================================
|
||||
echo.
|
||||
|
||||
REM Tjek at vi er i det rigtige bibliotek
|
||||
if not exist main.py (
|
||||
echo FEJL: Kør build.bat fra LinedanceAfspiller\linedance-app mappen
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Aktiver venv
|
||||
if not exist venv\Scripts\activate.bat (
|
||||
echo Opretter virtuelt miljø...
|
||||
python -m venv venv
|
||||
)
|
||||
call venv\Scripts\activate.bat
|
||||
|
||||
REM Installer/opdater pakker
|
||||
echo Installerer pakker...
|
||||
pip install -r requirements.txt --quiet
|
||||
pip install pyinstaller --quiet
|
||||
|
||||
REM Tjek VLC
|
||||
if not exist "C:\Program Files\VideoLAN\VLC\libvlc.dll" (
|
||||
if not exist "C:\Program Files (x86)\VideoLAN\VLC\libvlc.dll" (
|
||||
echo.
|
||||
echo ADVARSEL: VLC ser ikke ud til at vaere installeret!
|
||||
echo Download VLC fra: https://www.videolan.org/vlc/
|
||||
echo Vaelg 64-bit versionen.
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
REM Ryd gamle build-filer
|
||||
echo Rydder gamle build-filer...
|
||||
if exist build rmdir /s /q build
|
||||
if exist dist rmdir /s /q dist
|
||||
|
||||
REM Byg EXE
|
||||
echo.
|
||||
echo Bygger LineDancePlayer.exe ...
|
||||
echo (Dette tager typisk 1-3 minutter)
|
||||
echo.
|
||||
pyinstaller LineDancePlayer.spec
|
||||
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo FEJL under build! Se fejlbesked ovenfor.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo ================================================
|
||||
echo BUILD FAERDIG!
|
||||
echo Filen ligger i: dist\LineDancePlayer.exe
|
||||
echo ================================================
|
||||
echo.
|
||||
|
||||
REM Vis filstoerrelse
|
||||
for %%A in (dist\LineDancePlayer.exe) do echo Filstoerrelse: %%~zA bytes
|
||||
|
||||
pause
|
||||
161
linedance-app/LineDancePlayer.spec
Normal file
161
linedance-app/LineDancePlayer.spec
Normal file
@@ -0,0 +1,161 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
#
|
||||
# LineDancePlayer.spec
|
||||
#
|
||||
# Byg med: pyinstaller LineDancePlayer.spec
|
||||
# Output: dist\LineDancePlayer.exe
|
||||
#
|
||||
# Kræver: VLC installeret på byggemaskinen
|
||||
# (typisk C:\Program Files\VideoLAN\VLC)
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# ── Find VLC-installation ─────────────────────────────────────────────────────
|
||||
|
||||
def find_vlc_path() -> Path | None:
|
||||
"""Find VLC på Windows — tjekker de mest almindelige installationsstier."""
|
||||
candidates = [
|
||||
Path(os.environ.get("PROGRAMFILES", "C:/Program Files")) / "VideoLAN" / "VLC",
|
||||
Path(os.environ.get("PROGRAMFILES(X86)", "C:/Program Files (x86)")) / "VideoLAN" / "VLC",
|
||||
Path("C:/Program Files/VideoLAN/VLC"),
|
||||
Path("C:/Program Files (x86)/VideoLAN/VLC"),
|
||||
]
|
||||
# Tjek også PYTHONPATH og registry via python-vlc
|
||||
try:
|
||||
import vlc
|
||||
vlc_path = Path(vlc.plugin_path).parent if vlc.plugin_path else None
|
||||
if vlc_path and vlc_path.exists():
|
||||
candidates.insert(0, vlc_path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for path in candidates:
|
||||
if path.exists() and (path / "libvlc.dll").exists():
|
||||
return path
|
||||
return None
|
||||
|
||||
|
||||
VLC_PATH = find_vlc_path()
|
||||
if VLC_PATH is None:
|
||||
print("=" * 60)
|
||||
print("ADVARSEL: VLC ikke fundet!")
|
||||
print("Installer VLC fra https://www.videolan.org/vlc/")
|
||||
print("og kør pyinstaller igen.")
|
||||
print("=" * 60)
|
||||
VLC_PATH = Path("C:/Program Files/VideoLAN/VLC") # fallback
|
||||
|
||||
print(f"VLC fundet: {VLC_PATH}")
|
||||
|
||||
# ── Saml VLC binære filer ─────────────────────────────────────────────────────
|
||||
|
||||
vlc_binaries = []
|
||||
vlc_datas = []
|
||||
|
||||
if VLC_PATH.exists():
|
||||
# Hoved-DLL filer
|
||||
for dll in ["libvlc.dll", "libvlccore.dll", "libvlc.lib"]:
|
||||
dll_path = VLC_PATH / dll
|
||||
if dll_path.exists():
|
||||
vlc_binaries.append((str(dll_path), "."))
|
||||
|
||||
# Plugins-mappe — indeholder codecs, demuxers osv.
|
||||
plugins_dir = VLC_PATH / "plugins"
|
||||
if plugins_dir.exists():
|
||||
vlc_datas.append((str(plugins_dir), "plugins"))
|
||||
|
||||
# Locale-filer
|
||||
locale_dir = VLC_PATH / "locale"
|
||||
if locale_dir.exists():
|
||||
vlc_datas.append((str(locale_dir), "locale"))
|
||||
|
||||
# ── PyInstaller konfiguration ─────────────────────────────────────────────────
|
||||
|
||||
block_cipher = None
|
||||
|
||||
a = Analysis(
|
||||
["main.py"],
|
||||
pathex=["."],
|
||||
binaries=vlc_binaries,
|
||||
datas=[
|
||||
("ui", "ui"),
|
||||
("local", "local"),
|
||||
("player", "player"),
|
||||
] + vlc_datas,
|
||||
hiddenimports=[
|
||||
# PyQt6
|
||||
"PyQt6.sip",
|
||||
"PyQt6.QtCore",
|
||||
"PyQt6.QtGui",
|
||||
"PyQt6.QtWidgets",
|
||||
# Lyd og tags
|
||||
"vlc",
|
||||
"mutagen",
|
||||
"mutagen.mp3",
|
||||
"mutagen.id3",
|
||||
"mutagen.flac",
|
||||
"mutagen.mp4",
|
||||
"mutagen.oggvorbis",
|
||||
"mutagen.oggopus",
|
||||
# Fil-overvågning
|
||||
"watchdog",
|
||||
"watchdog.observers",
|
||||
"watchdog.observers.polling",
|
||||
"watchdog.events",
|
||||
# Database
|
||||
"sqlite3",
|
||||
# Standard
|
||||
"json",
|
||||
"pathlib",
|
||||
"threading",
|
||||
"urllib.request",
|
||||
"urllib.parse",
|
||||
],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[
|
||||
# Ting vi ikke bruger — reducerer filstørrelse
|
||||
"tkinter",
|
||||
"matplotlib",
|
||||
"numpy",
|
||||
"pandas",
|
||||
"scipy",
|
||||
"PIL",
|
||||
"cv2",
|
||||
"pytest",
|
||||
],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name="LineDancePlayer",
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True, # komprimer med UPX hvis tilgængeligt
|
||||
upx_exclude=[
|
||||
"libvlc.dll", # VLC må ikke komprimeres — den loader plugins dynamisk
|
||||
"libvlccore.dll",
|
||||
],
|
||||
runtime_tmpdir=None,
|
||||
console=False, # ingen konsol-vindue
|
||||
disable_windowed_traceback=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
# Ikon — kommenter ud hvis du ikke har en .ico fil endnu
|
||||
# icon="assets/icon.ico",
|
||||
)
|
||||
68
linedance-app/build.bat
Normal file
68
linedance-app/build.bat
Normal file
@@ -0,0 +1,68 @@
|
||||
@echo off
|
||||
echo ================================================
|
||||
echo LineDance Player - Byg EXE
|
||||
echo ================================================
|
||||
echo.
|
||||
|
||||
REM Tjek at vi er i det rigtige bibliotek
|
||||
if not exist main.py (
|
||||
echo FEJL: Kør build.bat fra LinedanceAfspiller\linedance-app mappen
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Aktiver venv
|
||||
if not exist venv\Scripts\activate.bat (
|
||||
echo Opretter virtuelt miljø...
|
||||
python -m venv venv
|
||||
)
|
||||
call venv\Scripts\activate.bat
|
||||
|
||||
REM Installer/opdater pakker
|
||||
echo Installerer pakker...
|
||||
pip install -r requirements.txt --quiet
|
||||
pip install pyinstaller --quiet
|
||||
|
||||
REM Tjek VLC
|
||||
if not exist "C:\Program Files\VideoLAN\VLC\libvlc.dll" (
|
||||
if not exist "C:\Program Files (x86)\VideoLAN\VLC\libvlc.dll" (
|
||||
echo.
|
||||
echo ADVARSEL: VLC ser ikke ud til at vaere installeret!
|
||||
echo Download VLC fra: https://www.videolan.org/vlc/
|
||||
echo Vaelg 64-bit versionen.
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
REM Ryd gamle build-filer
|
||||
echo Rydder gamle build-filer...
|
||||
if exist build rmdir /s /q build
|
||||
if exist dist rmdir /s /q dist
|
||||
|
||||
REM Byg EXE
|
||||
echo.
|
||||
echo Bygger LineDancePlayer.exe ...
|
||||
echo (Dette tager typisk 1-3 minutter)
|
||||
echo.
|
||||
pyinstaller LineDancePlayer.spec
|
||||
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo FEJL under build! Se fejlbesked ovenfor.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo ================================================
|
||||
echo BUILD FAERDIG!
|
||||
echo Filen ligger i: dist\LineDancePlayer.exe
|
||||
echo ================================================
|
||||
echo.
|
||||
|
||||
REM Vis filstoerrelse
|
||||
for %%A in (dist\LineDancePlayer.exe) do echo Filstoerrelse: %%~zA bytes
|
||||
|
||||
pause
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -259,10 +259,11 @@ def upsert_song(song_data: dict) -> str:
|
||||
song_id = existing["id"]
|
||||
conn.execute("""
|
||||
UPDATE songs SET
|
||||
title=?, artist=?, album=?, bpm=?, duration_sec=?,
|
||||
library_id=?, title=?, artist=?, album=?, bpm=?, duration_sec=?,
|
||||
file_format=?, file_modified_at=?, file_missing=0, extra_tags=?
|
||||
WHERE id=?
|
||||
""", (
|
||||
song_data.get("library_id"),
|
||||
song_data.get("title", ""),
|
||||
song_data.get("artist", ""),
|
||||
song_data.get("album", ""),
|
||||
|
||||
@@ -346,3 +346,46 @@ def read_dances_from_file(path: str | Path) -> list[str]:
|
||||
"""Læser kun danse fra en fil — hurtigere end fuld read_tags()."""
|
||||
tags = read_tags(path)
|
||||
return tags.get("dances", [])
|
||||
|
||||
|
||||
# ── BPM-analyse ───────────────────────────────────────────────────────────────
|
||||
|
||||
def analyze_bpm(path: str | Path) -> float | None:
|
||||
"""
|
||||
Analysér BPM fra lydfilen ved hjælp af librosa.
|
||||
Returnerer BPM som float eller None ved fejl.
|
||||
Tager 2-5 sekunder per sang — kør i baggrundstråd.
|
||||
"""
|
||||
try:
|
||||
import librosa
|
||||
# Indlæs kun de første 60 sekunder for hastighed
|
||||
y, sr = librosa.load(str(path), duration=60.0, mono=True)
|
||||
tempo, _ = librosa.beat.beat_track(y=y, sr=sr)
|
||||
# librosa returnerer array i nyere versioner
|
||||
if hasattr(tempo, "__len__"):
|
||||
bpm = float(tempo[0]) if len(tempo) > 0 else 0.0
|
||||
else:
|
||||
bpm = float(tempo)
|
||||
return round(bpm, 1) if bpm > 0 else None
|
||||
except ImportError:
|
||||
print("librosa ikke installeret — installer med: pip install librosa")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"BPM-analyse fejl for {path}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def analyze_and_save_bpm(path: str | Path, song_id: str) -> float | None:
|
||||
"""Analysér BPM og gem i SQLite. Returnerer målt BPM."""
|
||||
bpm = analyze_bpm(path)
|
||||
if bpm and bpm > 0:
|
||||
try:
|
||||
from local.local_db import get_db
|
||||
with get_db() as conn:
|
||||
conn.execute(
|
||||
"UPDATE songs SET bpm=? WHERE id=? AND (bpm IS NULL OR bpm=0)",
|
||||
(int(round(bpm)), song_id)
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"BPM gem fejl: {e}")
|
||||
return bpm
|
||||
|
||||
@@ -2,3 +2,6 @@ PyQt6>=6.6.0
|
||||
python-vlc>=3.0.18
|
||||
mutagen>=1.47.0
|
||||
watchdog>=4.0.0
|
||||
|
||||
# BPM-analyse
|
||||
librosa>=0.10.0
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -49,6 +49,11 @@ class LibraryManagerDialog(QDialog):
|
||||
btn_remove.clicked.connect(self._remove_selected)
|
||||
btn_row.addWidget(btn_remove)
|
||||
|
||||
btn_scan = QPushButton("⟳ Scan alle")
|
||||
btn_scan.setToolTip("Scan alle mapper for nye og ændrede filer")
|
||||
btn_scan.clicked.connect(self._scan_all)
|
||||
btn_row.addWidget(btn_scan)
|
||||
|
||||
btn_row.addStretch()
|
||||
btn_close = QPushButton("Luk")
|
||||
btn_close.clicked.connect(self.accept)
|
||||
@@ -83,6 +88,15 @@ class LibraryManagerDialog(QDialog):
|
||||
except Exception as e:
|
||||
print(f"Library manager load fejl: {e}")
|
||||
|
||||
def _scan_all(self):
|
||||
mw = self.parent()
|
||||
if hasattr(mw, "start_scan"):
|
||||
mw.start_scan()
|
||||
self._set_status("Scanning startet...")
|
||||
|
||||
def _set_status(self, text: str):
|
||||
pass # kan udvides med statuslinje i dialogen
|
||||
|
||||
def _add_folder(self):
|
||||
from PyQt6.QtWidgets import QFileDialog
|
||||
folder = QFileDialog.getExistingDirectory(self, "Vælg musikmappe")
|
||||
@@ -90,7 +104,9 @@ class LibraryManagerDialog(QDialog):
|
||||
mw = self.parent()
|
||||
if hasattr(mw, "add_library_path"):
|
||||
mw.add_library_path(folder)
|
||||
self._load()
|
||||
# Genindlæs listen efter kort pause så DB er opdateret
|
||||
from PyQt6.QtCore import QTimer
|
||||
QTimer.singleShot(600, self._load)
|
||||
|
||||
def _remove_selected(self):
|
||||
item = self._list.currentItem()
|
||||
|
||||
@@ -70,22 +70,11 @@ class LibraryPanel(QWidget):
|
||||
header.addWidget(lbl)
|
||||
header.addStretch()
|
||||
|
||||
self._btn_scan = QPushButton("⟳ SCAN")
|
||||
self._btn_scan.setFixedHeight(24)
|
||||
self._btn_scan.setToolTip("Scan alle biblioteksmapper for nye og ændrede filer")
|
||||
self._btn_scan.clicked.connect(self._on_scan_clicked)
|
||||
header.addWidget(self._btn_scan)
|
||||
|
||||
btn_manage = QPushButton("⚙ Mapper")
|
||||
btn_manage.setFixedHeight(24)
|
||||
btn_manage.setToolTip("Tilføj eller fjern musikbiblioteker")
|
||||
btn_manage.setToolTip("Tilføj, fjern og scan musikbiblioteker")
|
||||
btn_manage.clicked.connect(self._manage_libraries)
|
||||
header.addWidget(btn_manage)
|
||||
|
||||
btn_add = QPushButton("+ MAPPE")
|
||||
btn_add.setFixedHeight(24)
|
||||
btn_add.clicked.connect(self._add_folder)
|
||||
header.addWidget(btn_add)
|
||||
layout.addLayout(header)
|
||||
|
||||
# Scan status
|
||||
@@ -136,14 +125,10 @@ class LibraryPanel(QWidget):
|
||||
|
||||
def set_scanning(self, scanning: bool, status_text: str = ""):
|
||||
if scanning:
|
||||
self._btn_scan.setEnabled(False)
|
||||
self._btn_scan.setText("⟳ SCANNER...")
|
||||
self._scan_bar.show()
|
||||
self._scan_label.setText(status_text or "Starter...")
|
||||
self._scan_label.show()
|
||||
else:
|
||||
self._btn_scan.setEnabled(True)
|
||||
self._btn_scan.setText("⟳ SCAN")
|
||||
self._scan_bar.hide()
|
||||
self._scan_label.hide()
|
||||
|
||||
@@ -216,6 +201,7 @@ class LibraryPanel(QWidget):
|
||||
act_play = menu.addAction("Afspil")
|
||||
menu.addSeparator()
|
||||
act_tags = menu.addAction("✎ Rediger dans-tags...")
|
||||
act_bpm = menu.addAction("♩ Analysér BPM")
|
||||
menu.addSeparator()
|
||||
send_menu = menu.addMenu("Send til")
|
||||
act_mail = send_menu.addAction("✉ Send som mail")
|
||||
@@ -226,9 +212,39 @@ class LibraryPanel(QWidget):
|
||||
self.song_selected.emit(song)
|
||||
elif action == act_tags:
|
||||
self.edit_tags_requested.emit(song)
|
||||
elif action == act_bpm:
|
||||
self._analyze_bpm(song)
|
||||
elif action == act_mail:
|
||||
self.send_mail_requested.emit(song)
|
||||
|
||||
def _analyze_bpm(self, song: dict):
|
||||
"""Analysér BPM i baggrundstråd og opdater biblioteket."""
|
||||
path = song.get("local_path", "")
|
||||
song_id = song.get("id", "")
|
||||
if not path or not song_id:
|
||||
return
|
||||
from PyQt6.QtCore import QThread, pyqtSignal as _sig
|
||||
|
||||
class BpmWorker(QThread):
|
||||
done = _sig(float)
|
||||
def __init__(self, p, sid):
|
||||
super().__init__()
|
||||
self._p, self._sid = p, sid
|
||||
def run(self):
|
||||
from local.tag_reader import analyze_and_save_bpm
|
||||
bpm = analyze_and_save_bpm(self._p, self._sid)
|
||||
if bpm:
|
||||
self.done.emit(bpm)
|
||||
|
||||
self._bpm_worker = BpmWorker(path, song_id)
|
||||
self._bpm_worker.done.connect(
|
||||
lambda bpm: (
|
||||
self._do_search(),
|
||||
print(f"BPM analyseret: {bpm}")
|
||||
)
|
||||
)
|
||||
self._bpm_worker.start()
|
||||
|
||||
def _manage_libraries(self):
|
||||
from ui.library_manager import LibraryManagerDialog
|
||||
dialog = LibraryManagerDialog(parent=self.window())
|
||||
|
||||
@@ -115,38 +115,25 @@ class MainWindow(QMainWindow):
|
||||
def _build_menu(self):
|
||||
menubar = self.menuBar()
|
||||
|
||||
# Filer
|
||||
# ── Filer ─────────────────────────────────────────────────────────────
|
||||
file_menu = menubar.addMenu("Filer")
|
||||
|
||||
file_menu.addSeparator()
|
||||
|
||||
self._act_go_online = QAction("Gå online...", self)
|
||||
self._act_go_online.setShortcut("Ctrl+L")
|
||||
self._act_go_online.setToolTip("Log ind og synkroniser med server")
|
||||
self._act_go_online.triggered.connect(self._go_online)
|
||||
file_menu.addAction(self._act_go_online)
|
||||
|
||||
self._act_go_offline = QAction("Gå offline", self)
|
||||
self._act_go_offline.setToolTip("Log ud og arbejd lokalt")
|
||||
self._act_go_offline.triggered.connect(self._go_offline)
|
||||
self._act_go_offline.setEnabled(False) # kun aktiv når man er online
|
||||
self._act_go_offline.setEnabled(False)
|
||||
file_menu.addAction(self._act_go_offline)
|
||||
|
||||
file_menu.addSeparator()
|
||||
|
||||
act_add_folder = QAction("Tilføj musikmappe...", self)
|
||||
act_add_folder.setShortcut("Ctrl+O")
|
||||
act_add_folder.triggered.connect(self._menu_add_folder)
|
||||
file_menu.addAction(act_add_folder)
|
||||
|
||||
file_menu.addSeparator()
|
||||
|
||||
act_scan = QAction("Scan biblioteker", self)
|
||||
act_scan.setShortcut("Ctrl+R")
|
||||
act_scan.setToolTip("Gennemgå alle biblioteksmapper for nye og ændrede filer")
|
||||
act_scan.triggered.connect(self.start_scan)
|
||||
file_menu.addAction(act_scan)
|
||||
self._act_scan = act_scan
|
||||
act_settings = QAction("Indstillinger...", self)
|
||||
act_settings.setShortcut("Ctrl+,")
|
||||
act_settings.triggered.connect(self._open_settings)
|
||||
file_menu.addAction(act_settings)
|
||||
|
||||
file_menu.addSeparator()
|
||||
|
||||
@@ -155,33 +142,14 @@ class MainWindow(QMainWindow):
|
||||
act_quit.triggered.connect(self.close)
|
||||
file_menu.addAction(act_quit)
|
||||
|
||||
# Danseliste
|
||||
pl_menu = menubar.addMenu("Danseliste")
|
||||
# ── Ingen Danseliste- eller Visning-menu ──────────────────────────────
|
||||
# Ny/Gem/Hent ligger direkte i danseliste-panelet
|
||||
# Tema-skift ligger i topbar-knappen
|
||||
# Mapper og scan ligger i ⚙ Mapper dialogen
|
||||
|
||||
act_new_pl = QAction("Ny tom liste", self)
|
||||
act_new_pl.setShortcut("Ctrl+N")
|
||||
act_new_pl.triggered.connect(self._new_playlist)
|
||||
pl_menu.addAction(act_new_pl)
|
||||
|
||||
act_manage = QAction("Gem / Indlæs / Importer...", self)
|
||||
act_manage.setShortcut("Ctrl+M")
|
||||
act_manage.triggered.connect(self._open_playlist_manager)
|
||||
pl_menu.addAction(act_manage)
|
||||
|
||||
# Visning
|
||||
view_menu = menubar.addMenu("Visning")
|
||||
|
||||
act_theme = QAction("Skift tema (lyst/mørkt)", self)
|
||||
act_theme.setShortcut("Ctrl+T")
|
||||
act_theme.triggered.connect(self._toggle_theme)
|
||||
view_menu.addAction(act_theme)
|
||||
|
||||
view_menu.addSeparator()
|
||||
|
||||
act_settings = QAction("Indstillinger...", self)
|
||||
act_settings.setShortcut("Ctrl+,")
|
||||
act_settings.triggered.connect(self._open_settings)
|
||||
view_menu.addAction(act_settings)
|
||||
# Gem reference til scan-action (bruges stadig internt)
|
||||
self._act_scan = QAction("Scan", self)
|
||||
self._act_scan.triggered.connect(self.start_scan)
|
||||
|
||||
# ── Statuslinje ───────────────────────────────────────────────────────────
|
||||
|
||||
@@ -399,8 +367,10 @@ class MainWindow(QMainWindow):
|
||||
|
||||
init_db()
|
||||
|
||||
# Brug et Qt signal til thread-safe reload fra watcher-tråden
|
||||
from PyQt6.QtCore import QMetaObject, Q_ARG
|
||||
def on_file_change(event_type, path, song_id):
|
||||
QTimer.singleShot(500, self._reload_library)
|
||||
QTimer.singleShot(0, self._reload_library)
|
||||
|
||||
self._watcher = get_watcher(on_change=on_file_change)
|
||||
self._watcher.start()
|
||||
@@ -494,10 +464,16 @@ class MainWindow(QMainWindow):
|
||||
|
||||
def add_library_path(self, path: str):
|
||||
try:
|
||||
if not self._watcher:
|
||||
self._set_status("Watcher ikke klar endnu — prøv igen om et øjeblik", 3000)
|
||||
return
|
||||
self._watcher.add_library(path)
|
||||
self._set_status(f"Tilføjet: {path} — scanner...")
|
||||
# Genindlæs bibliotekslisten og start scan
|
||||
QTimer.singleShot(500, self._reload_library)
|
||||
QTimer.singleShot(1000, self.start_scan)
|
||||
except Exception as e:
|
||||
self._set_status(f"Fejl: {e}")
|
||||
self._set_status(f"Fejl ved tilføjelse: {e}")
|
||||
|
||||
def _open_settings(self):
|
||||
dialog = SettingsDialog(parent=self)
|
||||
@@ -860,10 +836,15 @@ class MainWindow(QMainWindow):
|
||||
self._vu.reset()
|
||||
|
||||
# Markér den afspillede sang
|
||||
self._playlist_panel.mark_played(self._current_idx)
|
||||
prev_idx = self._current_idx
|
||||
self._playlist_panel.mark_played(prev_idx)
|
||||
|
||||
# Find næste afspilbare sang — spring skippede og afspillede over
|
||||
ni = self._playlist_panel.next_playable_idx(self._current_idx + 1)
|
||||
# Synkroniser event-status til den gemte navngivne liste
|
||||
self._sync_event_status_to_playlist()
|
||||
|
||||
# Find næste afspilbare sang — fra 0 hvis ingen sang var i gang
|
||||
search_from = max(0, prev_idx + 1)
|
||||
ni = self._playlist_panel.next_playable_idx(search_from)
|
||||
next_song = self._playlist_panel.get_song(ni) if ni is not None else None
|
||||
if next_song:
|
||||
self._current_idx = ni
|
||||
@@ -876,6 +857,29 @@ class MainWindow(QMainWindow):
|
||||
self._lbl_dances.setText("")
|
||||
self._set_status("Danselisten er afsluttet")
|
||||
|
||||
def _sync_event_status_to_playlist(self):
|
||||
"""Gem event-fremgang i den aktive navngivne liste."""
|
||||
try:
|
||||
from local.local_db import get_db
|
||||
songs = self._playlist_panel.get_songs()
|
||||
statuses = self._playlist_panel.get_statuses()
|
||||
with get_db() as conn:
|
||||
# Find den aktive liste (ikke __aktiv__)
|
||||
pl = conn.execute(
|
||||
"SELECT id FROM playlists WHERE name != '__aktiv__' "
|
||||
"ORDER BY created_at DESC LIMIT 1"
|
||||
).fetchone()
|
||||
if not pl:
|
||||
return
|
||||
# Opdater status for hver sang i listen
|
||||
for i, (song, status) in enumerate(zip(songs, statuses)):
|
||||
conn.execute("""
|
||||
UPDATE playlist_songs SET status=?
|
||||
WHERE playlist_id=? AND song_id=?
|
||||
""", (status, pl["id"], song.get("id")))
|
||||
except Exception as e:
|
||||
print(f"Event-status sync fejl: {e}")
|
||||
|
||||
def _on_state_changed(self, state: str):
|
||||
if state == "playing":
|
||||
self._btn_play.setText("⏸")
|
||||
|
||||
@@ -216,6 +216,28 @@ QScrollBar::handle:vertical {
|
||||
}
|
||||
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0; }
|
||||
|
||||
/* Højreklik-menu */
|
||||
QMenu {
|
||||
background-color: #22252a;
|
||||
color: #e8eaf0;
|
||||
border: 1px solid #4a5060;
|
||||
padding: 4px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
QMenu::item {
|
||||
padding: 8px 24px;
|
||||
border-radius: 0;
|
||||
}
|
||||
QMenu::item:selected {
|
||||
background-color: #e8a020;
|
||||
color: #111214;
|
||||
}
|
||||
QMenu::separator {
|
||||
height: 1px;
|
||||
background: #3a3e46;
|
||||
margin: 4px 8px;
|
||||
}
|
||||
|
||||
/* Topbar */
|
||||
QFrame#topbar {
|
||||
background-color: #1a1c1f;
|
||||
@@ -247,7 +269,7 @@ QMainWindow, #root {
|
||||
}
|
||||
QPushButton {
|
||||
background-color: #b0b4bc;
|
||||
color: #4a5060;
|
||||
color: #1a1c22;
|
||||
border-color: #8890a0;
|
||||
}
|
||||
QPushButton:hover {
|
||||
@@ -262,10 +284,19 @@ QPushButton#btn_play {
|
||||
}
|
||||
QListWidget {
|
||||
background-color: #d8dae0;
|
||||
color: #1a1c22;
|
||||
}
|
||||
QListWidget::item {
|
||||
color: #1a1c22;
|
||||
}
|
||||
QListWidget::item:selected {
|
||||
background-color: #eef0f4;
|
||||
border-left: 2px solid #c07010;
|
||||
background-color: #c07010;
|
||||
color: #ffffff;
|
||||
border-left: 2px solid #a05808;
|
||||
}
|
||||
QListWidget::item:hover {
|
||||
background-color: #c8ccd4;
|
||||
color: #1a1c22;
|
||||
}
|
||||
QLineEdit {
|
||||
background-color: #c8cad0;
|
||||
@@ -280,12 +311,22 @@ QFrame#transport_frame, QFrame#progress_frame {
|
||||
}
|
||||
QFrame#track_display { background-color: #c8cad0; border-color: #aab0bc; }
|
||||
QFrame#topbar { background-color: #d8dae0; border-color: #aab0bc; }
|
||||
QLabel#section_title { background-color: #e4e6ec; color: #8890a0; border-color: #aab0bc; }
|
||||
QLabel#section_title { background-color: #e4e6ec; color: #1a1c22; border-color: #aab0bc; }
|
||||
QLabel#track_title { color: #1a1c22; }
|
||||
QLabel#track_meta { color: #4a5060; }
|
||||
QLabel#result_count { color: #5a6070; }
|
||||
QSlider::groove:horizontal { background: #b0b4bc; }
|
||||
QScrollBar:vertical { background: #d8dae0; }
|
||||
QScrollBar::handle:vertical { background: #8890a0; }
|
||||
QMenu {
|
||||
background-color: #e4e6ec;
|
||||
color: #1a1c22;
|
||||
border: 1px solid #aab0bc;
|
||||
}
|
||||
QMenu::item:selected {
|
||||
background-color: #c07010;
|
||||
color: #ffffff;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
|
||||
8
linedance-app/venv/bin/f2py
Executable file
8
linedance-app/venv/bin/f2py
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/home/carsten/Dokumenter/GitClone/LinedanceAfspiller/linedance-app/venv/bin/python3.12
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from numpy.f2py.f2py2e import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
linedance-app/venv/bin/normalizer
Executable file
8
linedance-app/venv/bin/normalizer
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/home/carsten/Dokumenter/GitClone/LinedanceAfspiller/linedance-app/venv/bin/python3.12
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from charset_normalizer.cli import cli_detect
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(cli_detect())
|
||||
8
linedance-app/venv/bin/numba
Executable file
8
linedance-app/venv/bin/numba
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/home/carsten/Dokumenter/GitClone/LinedanceAfspiller/linedance-app/venv/bin/python3.12
|
||||
# -*- coding: UTF-8 -*-
|
||||
from __future__ import print_function, division, absolute_import
|
||||
|
||||
from numba.misc.numba_entry import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
8
linedance-app/venv/bin/numpy-config
Executable file
8
linedance-app/venv/bin/numpy-config
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/home/carsten/Dokumenter/GitClone/LinedanceAfspiller/linedance-app/venv/bin/python3.12
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from numpy._configtool import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,11 @@
|
||||
# auto-generated file
|
||||
import _cffi_backend
|
||||
|
||||
ffi = _cffi_backend.FFI('_soundfile',
|
||||
_version = 0x2601,
|
||||
_types = b'\x00\x00\x12\x0D\x00\x00\x68\x03\x00\x00\x07\x01\x00\x00\x67\x03\x00\x00\x75\x03\x00\x00\x00\x0F\x00\x00\x12\x0D\x00\x00\x6A\x03\x00\x00\x07\x01\x00\x00\x03\x11\x00\x00\x00\x0F\x00\x00\x12\x0D\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x03\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x07\x0D\x00\x00\x69\x03\x00\x00\x00\x0F\x00\x00\x07\x0D\x00\x00\x12\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x07\x0D\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x07\x0D\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x67\x03\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x12\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x12\x11\x00\x00\x6A\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x12\x11\x00\x00\x07\x01\x00\x00\x07\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x12\x11\x00\x00\x07\x01\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x36\x0D\x00\x00\x12\x11\x00\x00\x6B\x03\x00\x00\x17\x01\x00\x00\x00\x0F\x00\x00\x36\x0D\x00\x00\x12\x11\x00\x00\x6F\x03\x00\x00\x17\x01\x00\x00\x00\x0F\x00\x00\x36\x0D\x00\x00\x12\x11\x00\x00\x02\x03\x00\x00\x17\x01\x00\x00\x00\x0F\x00\x00\x36\x0D\x00\x00\x12\x11\x00\x00\x17\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x36\x0D\x00\x00\x12\x11\x00\x00\x74\x03\x00\x00\x17\x01\x00\x00\x00\x0F\x00\x00\x36\x0D\x00\x00\x12\x11\x00\x00\x04\x11\x00\x00\x17\x01\x00\x00\x00\x0F\x00\x00\x36\x0D\x00\x00\x17\x01\x00\x00\x07\x01\x00\x00\x04\x11\x00\x00\x00\x0F\x00\x00\x36\x0D\x00\x00\x04\x11\x00\x00\x00\x0F\x00\x00\x36\x0D\x00\x00\x04\x11\x00\x00\x17\x01\x00\x00\x04\x11\x00\x00\x00\x0F\x00\x00\x36\x0D\x00\x00\x75\x03\x00\x00\x17\x01\x00\x00\x04\x11\x00\x00\x00\x0F\x00\x00\x75\x0D\x00\x00\x12\x11\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\x01\x09\x00\x00\x02\x09\x00\x00\x03\x09\x00\x00\x02\x01\x00\x00\x0E\x01\x00\x00\x00\x0B\x00\x00\x01\x0B\x00\x00\x02\x0B\x00\x00\x0D\x01\x00\x00\x51\x03\x00\x00\x56\x03\x00\x00\x59\x03\x00\x00\x5E\x03\x00\x00\x05\x01\x00\x00\x00\x01',
|
||||
_globals = (b'\xFF\xFF\xFF\x0BSFC_FILE_TRUNCATE',4224,b'\xFF\xFF\xFF\x0BSFC_GET_FORMAT_INFO',4136,b'\xFF\xFF\xFF\x0BSFC_GET_FORMAT_MAJOR',4145,b'\xFF\xFF\xFF\x0BSFC_GET_FORMAT_MAJOR_COUNT',4144,b'\xFF\xFF\xFF\x0BSFC_GET_FORMAT_SUBTYPE',4147,b'\xFF\xFF\xFF\x0BSFC_GET_FORMAT_SUBTYPE_COUNT',4146,b'\xFF\xFF\xFF\x0BSFC_GET_LIB_VERSION',4096,b'\xFF\xFF\xFF\x0BSFC_GET_LOG_INFO',4097,b'\xFF\xFF\xFF\x0BSFC_SET_BITRATE_MODE',4869,b'\xFF\xFF\xFF\x0BSFC_SET_CLIPPING',4288,b'\xFF\xFF\xFF\x0BSFC_SET_COMPRESSION_LEVEL',4865,b'\xFF\xFF\xFF\x0BSFC_SET_SCALE_FLOAT_INT_READ',4116,b'\xFF\xFF\xFF\x0BSFC_SET_SCALE_INT_FLOAT_WRITE',4117,b'\xFF\xFF\xFF\x0BSFM_RDWR',48,b'\xFF\xFF\xFF\x0BSFM_READ',16,b'\xFF\xFF\xFF\x0BSFM_WRITE',32,b'\xFF\xFF\xFF\x0BSF_BITRATE_MODE_AVERAGE',1,b'\xFF\xFF\xFF\x0BSF_BITRATE_MODE_CONSTANT',0,b'\xFF\xFF\xFF\x0BSF_BITRATE_MODE_VARIABLE',2,b'\xFF\xFF\xFF\x0BSF_FALSE',0,b'\xFF\xFF\xFF\x0BSF_FORMAT_ENDMASK',805306368,b'\xFF\xFF\xFF\x0BSF_FORMAT_SUBMASK',65535,b'\xFF\xFF\xFF\x0BSF_FORMAT_TYPEMASK',268369920,b'\xFF\xFF\xFF\x0BSF_TRUE',1,b'\x00\x00\x20\x23sf_close',0,b'\x00\x00\x2D\x23sf_command',0,b'\x00\x00\x20\x23sf_error',0,b'\x00\x00\x18\x23sf_error_number',0,b'\x00\x00\x23\x23sf_error_str',0,b'\x00\x00\x1D\x23sf_format_check',0,b'\x00\x00\x14\x23sf_get_string',0,b'\x00\x00\x06\x23sf_open',0,b'\x00\x00\x0B\x23sf_open_fd',0,b'\x00\x00\x00\x23sf_open_virtual',0,b'\x00\x00\x20\x23sf_perror',0,b'\x00\x00\x33\x23sf_read_double',0,b'\x00\x00\x38\x23sf_read_float',0,b'\x00\x00\x3D\x23sf_read_int',0,b'\x00\x00\x4C\x23sf_read_raw',0,b'\x00\x00\x47\x23sf_read_short',0,b'\x00\x00\x4C\x23sf_readf_double',0,b'\x00\x00\x4C\x23sf_readf_float',0,b'\x00\x00\x4C\x23sf_readf_int',0,b'\x00\x00\x4C\x23sf_readf_short',0,b'\x00\x00\x42\x23sf_seek',0,b'\x00\x00\x28\x23sf_set_string',0,b'\x00\x00\x11\x23sf_strerror',0,b'\x00\x00\x1B\x23sf_version_string',0,b'\x00\x00\x33\x23sf_write_double',0,b'\x00\x00\x38\x23sf_write_float',0,b'\x00\x00\x3D\x23sf_write_int',0,b'\x00\x00\x4C\x23sf_write_raw',0,b'\x00\x00\x47\x23sf_write_short',0,b'\x00\x00\x63\x23sf_write_sync',0,b'\x00\x00\x4C\x23sf_writef_double',0,b'\x00\x00\x4C\x23sf_writef_float',0,b'\x00\x00\x4C\x23sf_writef_int',0,b'\x00\x00\x4C\x23sf_writef_short',0),
|
||||
_struct_unions = ((b'\x00\x00\x00\x66\x00\x00\x00\x02SF_FORMAT_INFO',b'\x00\x00\x02\x11format',b'\x00\x00\x07\x11name',b'\x00\x00\x07\x11extension'),(b'\x00\x00\x00\x67\x00\x00\x00\x02SF_INFO',b'\x00\x00\x36\x11frames',b'\x00\x00\x02\x11samplerate',b'\x00\x00\x02\x11channels',b'\x00\x00\x02\x11format',b'\x00\x00\x02\x11sections',b'\x00\x00\x02\x11seekable'),(b'\x00\x00\x00\x68\x00\x00\x00\x02SF_VIRTUAL_IO',b'\x00\x00\x71\x11get_filelen',b'\x00\x00\x70\x11seek',b'\x00\x00\x72\x11read',b'\x00\x00\x73\x11write',b'\x00\x00\x71\x11tell'),(b'\x00\x00\x00\x69\x00\x00\x00\x10SNDFILE_tag',)),
|
||||
_enums = (b'\x00\x00\x00\x6C\x00\x00\x00\x16$1\x00SF_FORMAT_SUBMASK,SF_FORMAT_TYPEMASK,SF_FORMAT_ENDMASK',b'\x00\x00\x00\x6D\x00\x00\x00\x16$2\x00SFC_GET_LIB_VERSION,SFC_GET_LOG_INFO,SFC_GET_FORMAT_INFO,SFC_GET_FORMAT_MAJOR_COUNT,SFC_GET_FORMAT_MAJOR,SFC_GET_FORMAT_SUBTYPE_COUNT,SFC_GET_FORMAT_SUBTYPE,SFC_FILE_TRUNCATE,SFC_SET_CLIPPING,SFC_SET_SCALE_FLOAT_INT_READ,SFC_SET_SCALE_INT_FLOAT_WRITE,SFC_SET_COMPRESSION_LEVEL,SFC_SET_BITRATE_MODE',b'\x00\x00\x00\x6E\x00\x00\x00\x16$3\x00SF_FALSE,SF_TRUE,SFM_READ,SFM_WRITE,SFM_RDWR,SF_BITRATE_MODE_CONSTANT,SF_BITRATE_MODE_AVERAGE,SF_BITRATE_MODE_VARIABLE'),
|
||||
_typenames = (b'\x00\x00\x00\x66SF_FORMAT_INFO',b'\x00\x00\x00\x67SF_INFO',b'\x00\x00\x00\x68SF_VIRTUAL_IO',b'\x00\x00\x00\x69SNDFILE',b'\x00\x00\x00\x36sf_count_t',b'\x00\x00\x00\x71sf_vio_get_filelen',b'\x00\x00\x00\x72sf_vio_read',b'\x00\x00\x00\x70sf_vio_seek',b'\x00\x00\x00\x71sf_vio_tell',b'\x00\x00\x00\x73sf_vio_write'),
|
||||
)
|
||||
@@ -0,0 +1,503 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# this file makes _soundfile_data importable, so we can query its path
|
||||
# when searching for the libsndfile binaries.
|
||||
pass
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -0,0 +1,254 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: audioread
|
||||
Version: 3.1.0
|
||||
Summary: Multi-library, cross-platform audio decoding.
|
||||
License-Expression: MIT
|
||||
License-File: LICENSE
|
||||
Author: Adrian Sampson
|
||||
Author-email: adrian@radbox.org
|
||||
Requires-Python: >=3.9
|
||||
Classifier: Topic :: Multimedia :: Sound/Audio :: Conversion
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Provides-Extra: gi
|
||||
Provides-Extra: mad
|
||||
Provides-Extra: test
|
||||
Requires-Dist: pygobject (>=3.54.2,<4.0.0) ; extra == "gi"
|
||||
Requires-Dist: pymad[mad] (>=0.11.3,<0.12.0) ; extra == "mad"
|
||||
Requires-Dist: pytest (>=8.4.2) ; extra == "test"
|
||||
Requires-Dist: pytest-cov (>=7.0.0) ; extra == "test"
|
||||
Requires-Dist: standard-aifc ; python_version >= "3.13"
|
||||
Requires-Dist: standard-sunau ; python_version >= "3.13"
|
||||
Project-URL: Bug Tracker, https://github.com/beetbox/audioread/issues
|
||||
Project-URL: Homepage, https://github.com/beetbox/audioread
|
||||
Project-URL: Repository, https://github.com/beetbox/audioread
|
||||
Description-Content-Type: text/x-rst
|
||||
|
||||
audioread
|
||||
=========
|
||||
|
||||
Decode audio files using whichever backend is available. The library
|
||||
currently supports:
|
||||
|
||||
- `Gstreamer`_ via `PyGObject`_.
|
||||
- `Core Audio`_ on Mac OS X via `ctypes`_. (PyObjC not required.)
|
||||
- `MAD`_ via the `pymad`_ bindings.
|
||||
- `FFmpeg`_ or `Libav`_ via its command-line interface.
|
||||
- The standard library `wave`_, `aifc`_, and `sunau`_ modules (for
|
||||
uncompressed audio formats).
|
||||
|
||||
.. _Gstreamer: http://gstreamer.freedesktop.org/
|
||||
.. _gst-python: http://gstreamer.freedesktop.org/modules/gst-python.html
|
||||
.. _Core Audio: http://developer.apple.com/technologies/mac/audio-and-video.html
|
||||
.. _ctypes: http://docs.python.org/library/ctypes.html
|
||||
.. _MAD: http://www.underbit.com/products/mad/
|
||||
.. _pymad: http://spacepants.org/src/pymad/
|
||||
.. _FFmpeg: http://ffmpeg.org/
|
||||
.. _Libav: https://www.libav.org/
|
||||
.. _wave: http://docs.python.org/library/wave.html
|
||||
.. _aifc: http://docs.python.org/library/aifc.html
|
||||
.. _sunau: http://docs.python.org/library/sunau.html
|
||||
.. _PyGObject: https://pygobject.readthedocs.io/
|
||||
|
||||
Use the library like so::
|
||||
|
||||
with audioread.audio_open(filename) as f:
|
||||
print(f.channels, f.samplerate, f.duration)
|
||||
for buf in f:
|
||||
do_something(buf)
|
||||
|
||||
Buffers in the file can be accessed by iterating over the object returned from
|
||||
``audio_open``. Each buffer is a bytes-like object (``buffer``, ``bytes``, or
|
||||
``bytearray``) containing raw **16-bit little-endian signed integer PCM
|
||||
data**. (Currently, these PCM format parameters are not configurable, but this
|
||||
could be added to most of the backends.)
|
||||
|
||||
Additional values are available as fields on the audio file object:
|
||||
|
||||
- ``channels`` is the number of audio channels (an integer).
|
||||
- ``samplerate`` is given in Hz (an integer).
|
||||
- ``duration`` is the length of the audio in seconds (a float).
|
||||
|
||||
The ``audio_open`` function transparently selects a backend that can read the
|
||||
file. (Each backend is implemented in a module inside the ``audioread``
|
||||
package.) If no backends succeed in opening the file, a ``DecodeError``
|
||||
exception is raised. This exception is only used when the file type is
|
||||
unsupported by the backends; if the file doesn't exist, a standard ``IOError``
|
||||
will be raised.
|
||||
|
||||
A second optional parameter to ``audio_open`` specifies which backends to try
|
||||
(instead of trying them all, which is the default). You can use the
|
||||
``available_backends`` function to get a list backends that are usable on the
|
||||
current system.
|
||||
|
||||
Audioread supports Python 3 (3.9+).
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
The included ``decode.py`` script demonstrates using this package to
|
||||
convert compressed audio files to WAV files.
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
A ``NoBackendError`` exception means that the library could not find one of
|
||||
the libraries or tools it needs to decode audio. This could mean, for example,
|
||||
that you have a broken installation of `FFmpeg`_. To check, try typing
|
||||
``ffmpeg -version`` in your shell. If that gives you an error, try installing
|
||||
FFmpeg with your OS's package manager (e.g., apt or yum) or `using Conda
|
||||
<https://anaconda.org/conda-forge/ffmpeg>`_.
|
||||
|
||||
Version History
|
||||
---------------
|
||||
|
||||
3.0.2
|
||||
Support path-like objects (not just strings) in the Core Audio backend.
|
||||
|
||||
3.0.1
|
||||
Fix a possible deadlock when FFmpeg's version output produces too much data.
|
||||
|
||||
3.0.0
|
||||
Drop support for Python 2 and older versions of Python 3. The library now
|
||||
requires Python 3.6+.
|
||||
Increase default block size in FFmpegAudioFile to get slightly faster file reading.
|
||||
Cache backends for faster lookup (thanks to @bmcfee).
|
||||
Audio file classes now inherit from a common base ``AudioFile`` class.
|
||||
|
||||
2.1.9
|
||||
Work correctly with GStreamer 1.18 and later (thanks to @ssssam).
|
||||
|
||||
2.1.8
|
||||
Fix an unhandled ``OSError`` when FFmpeg is not installed.
|
||||
|
||||
2.1.7
|
||||
Properly close some filehandles in the FFmpeg backend (thanks to
|
||||
@RyanMarcus and @ssssam).
|
||||
The maddec backend now always produces bytes objects, like the other
|
||||
backends (thanks to @ssssam).
|
||||
Resolve an audio data memory leak in the GStreamer backend (thanks again to
|
||||
@ssssam).
|
||||
You can now optionally specify which specific backends ``audio_open`` should
|
||||
try (thanks once again to @ssssam).
|
||||
On Windows, avoid opening a console window to run FFmpeg (thanks to @flokX).
|
||||
|
||||
2.1.6
|
||||
Fix a "no such process" crash in the FFmpeg backend on Windows Subsystem for
|
||||
Linux (thanks to @llamasoft).
|
||||
Avoid suppressing SIGINT in the GStreamer backend on older versions of
|
||||
PyGObject (thanks to @lazka).
|
||||
|
||||
2.1.5
|
||||
Properly clean up the file handle when a backend fails to decode a file.
|
||||
Fix parsing of "N.M" channel counts in the FFmpeg backend (thanks to @piem).
|
||||
Avoid a crash in the raw backend when a file uses an unsupported number of
|
||||
bits per sample (namely, 24-bit samples in Python < 3.4).
|
||||
Add a ``__version__`` value to the package.
|
||||
|
||||
2.1.4
|
||||
Fix a bug in the FFmpeg backend where, after closing a file, the program's
|
||||
standard input stream would be "broken" and wouldn't receive any input.
|
||||
|
||||
2.1.3
|
||||
Avoid some warnings in the GStreamer backend when using modern versions of
|
||||
GLib. We now require at least GLib 2.32.
|
||||
|
||||
2.1.2
|
||||
Fix a file descriptor leak when opening and closing many files using
|
||||
GStreamer.
|
||||
|
||||
2.1.1
|
||||
Just fix ReST formatting in the README.
|
||||
|
||||
2.1.0
|
||||
The FFmpeg backend can now also use Libav's ``avconv`` command.
|
||||
Fix a warning by requiring GStreamer >= 1.0.
|
||||
Fix some Python 3 crashes with the new GStreamer backend (thanks to
|
||||
@xix-xeaon).
|
||||
|
||||
2.0.0
|
||||
The GStreamer backend now uses GStreamer 1.x via the new
|
||||
gobject-introspection API (and is compatible with Python 3).
|
||||
|
||||
1.2.2
|
||||
When running FFmpeg on Windows, disable its crash dialog. Thanks to
|
||||
jcsaaddupuy.
|
||||
|
||||
1.2.1
|
||||
Fix an unhandled exception when opening non-raw audio files (thanks to
|
||||
aostanin).
|
||||
Fix Python 3 compatibility for the raw-file backend.
|
||||
|
||||
1.2.0
|
||||
Add support for FFmpeg on Windows (thanks to Jean-Christophe Saad-Dupuy).
|
||||
|
||||
1.1.0
|
||||
Add support for Sun/NeXT `Au files`_ via the standard-library ``sunau``
|
||||
module (thanks to Dan Ellis).
|
||||
|
||||
1.0.3
|
||||
Use the rawread (standard-library) backend for .wav files.
|
||||
|
||||
1.0.2
|
||||
Send SIGKILL, not SIGTERM, to ffmpeg processes to avoid occasional hangs.
|
||||
|
||||
1.0.1
|
||||
When GStreamer fails to report a duration, raise an exception instead of
|
||||
silently setting the duration field to None.
|
||||
|
||||
1.0.0
|
||||
Catch GStreamer's exception when necessary components, such as
|
||||
``uridecodebin``, are missing.
|
||||
The GStreamer backend now accepts relative paths.
|
||||
Fix a hang in GStreamer when the stream finishes before it begins (when
|
||||
reading broken files).
|
||||
Initial support for Python 3.
|
||||
|
||||
0.8
|
||||
All decoding errors are now subclasses of ``DecodeError``.
|
||||
|
||||
0.7
|
||||
Fix opening WAV and AIFF files via Unicode filenames.
|
||||
|
||||
0.6
|
||||
Make FFmpeg timeout more robust.
|
||||
Dump FFmpeg output on timeout.
|
||||
Fix a nondeterministic hang in the Gstreamer backend.
|
||||
Fix a file descriptor leak in the MAD backend.
|
||||
|
||||
0.5
|
||||
Fix crash when FFmpeg fails to report a duration.
|
||||
Fix a hang when FFmpeg fills up its stderr output buffer.
|
||||
Add a timeout to ``ffmpeg`` tool execution (currently 10 seconds for each
|
||||
4096-byte read); a ``ReadTimeoutError`` exception is raised if the tool times
|
||||
out.
|
||||
|
||||
0.4
|
||||
Fix channel count detection for FFmpeg backend.
|
||||
|
||||
0.3
|
||||
Fix a problem with the Gstreamer backend where audio files could be left open
|
||||
even after the ``GstAudioFile`` was "closed".
|
||||
|
||||
0.2
|
||||
Fix a hang in the GStreamer backend that occurs occasionally on some
|
||||
platforms.
|
||||
|
||||
0.1
|
||||
Initial release.
|
||||
|
||||
.. _Au files: http://en.wikipedia.org/wiki/Au_file_format
|
||||
|
||||
Et Cetera
|
||||
---------
|
||||
|
||||
``audioread`` is by Adrian Sampson. It is made available under `the MIT
|
||||
license`_. An alternative to this module is `decoder.py`_.
|
||||
|
||||
.. _the MIT license: http://www.opensource.org/licenses/mit-license.php
|
||||
.. _decoder.py: http://www.brailleweb.com/cgi-bin/python.py
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
audioread-3.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
audioread-3.1.0.dist-info/METADATA,sha256=-4vYuQK6m6VtD5sRyvAxgZsS_V0cWYFeN3ZZjSkfaEY,8977
|
||||
audioread-3.1.0.dist-info/RECORD,,
|
||||
audioread-3.1.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
||||
audioread-3.1.0.dist-info/licenses/LICENSE,sha256=4A__aKdaWCEyhC4zQmcwaZJVmG8d7DYiUvdCPbAnAZ0,1063
|
||||
audioread/__init__.py,sha256=9V6iiksBdHdKTHUqkf30IL1fS_1_IrCIytLLmonkYP8,3540
|
||||
audioread/__pycache__/__init__.cpython-312.pyc,,
|
||||
audioread/__pycache__/base.cpython-312.pyc,,
|
||||
audioread/__pycache__/exceptions.cpython-312.pyc,,
|
||||
audioread/__pycache__/ffdec.cpython-312.pyc,,
|
||||
audioread/__pycache__/gstdec.cpython-312.pyc,,
|
||||
audioread/__pycache__/macca.cpython-312.pyc,,
|
||||
audioread/__pycache__/maddec.cpython-312.pyc,,
|
||||
audioread/__pycache__/rawread.cpython-312.pyc,,
|
||||
audioread/base.py,sha256=AO1WKrUUQtrh3hCuvHJaAu_HWQnIVXLvkQyOCWFtWhU,725
|
||||
audioread/exceptions.py,sha256=RTwYBpMlBy4bWPeSxidoz69YCXjTtpHrHFMuHWiJ6h0,962
|
||||
audioread/ffdec.py,sha256=A8kcImseS99YywzNsuK8DsORho-6vyI79XeJ92CN-YQ,10541
|
||||
audioread/gstdec.py,sha256=ksh08sEgN-bLVSoITod0QkeQhXDh7s1_3BMUwTGCu2s,14643
|
||||
audioread/macca.py,sha256=aVniGv1pnEndClGvw0Bw5cRQp2SIRdxY78r_4DfW8l0,10899
|
||||
audioread/maddec.py,sha256=9MbadGkBIYXVgzZq6cgYCV2FAZwVk8AWTYrsWyee98g,2518
|
||||
audioread/rawread.py,sha256=eLA23jT41c1e0nyDmnXJrKwgFo4mNBrILhVzDPr4au8,4322
|
||||
@@ -0,0 +1,4 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: poetry-core 2.2.1
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2011-2018 Adrian Sampson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,131 @@
|
||||
# This file is part of audioread.
|
||||
# Copyright 2013, Adrian Sampson.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
"""Multi-library, cross-platform audio decoding."""
|
||||
|
||||
from . import ffdec
|
||||
from .exceptions import DecodeError, NoBackendError
|
||||
from .base import AudioFile # noqa
|
||||
|
||||
|
||||
def _gst_available():
|
||||
"""Determine whether Gstreamer and the Python GObject bindings are
|
||||
installed.
|
||||
"""
|
||||
try:
|
||||
import gi
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
try:
|
||||
gi.require_version('Gst', '1.0')
|
||||
except (ValueError, AttributeError):
|
||||
return False
|
||||
|
||||
try:
|
||||
from gi.repository import Gst # noqa
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _ca_available():
|
||||
"""Determines whether CoreAudio is available (i.e., we're running on
|
||||
Mac OS X).
|
||||
"""
|
||||
import ctypes.util
|
||||
lib = ctypes.util.find_library('AudioToolbox')
|
||||
return lib is not None
|
||||
|
||||
|
||||
def _mad_available():
|
||||
"""Determines whether the pymad bindings are available."""
|
||||
try:
|
||||
import mad # noqa
|
||||
except ImportError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
# A cache for the available backends.
|
||||
BACKENDS = []
|
||||
|
||||
|
||||
def available_backends(flush_cache=False):
|
||||
"""Returns a list of backends that are available on this system.
|
||||
|
||||
The list of backends is cached after the first call.
|
||||
If the parameter `flush_cache` is set to `True`, then the cache
|
||||
will be flushed and the backend list will be reconstructed.
|
||||
"""
|
||||
|
||||
if BACKENDS and not flush_cache:
|
||||
return BACKENDS
|
||||
|
||||
# Standard-library WAV and AIFF readers.
|
||||
from . import rawread
|
||||
result = [rawread.RawAudioFile]
|
||||
|
||||
# Core Audio.
|
||||
if _ca_available():
|
||||
from . import macca
|
||||
result.append(macca.ExtAudioFile)
|
||||
|
||||
# GStreamer.
|
||||
if _gst_available():
|
||||
from . import gstdec
|
||||
result.append(gstdec.GstAudioFile)
|
||||
|
||||
# MAD.
|
||||
if _mad_available():
|
||||
from . import maddec
|
||||
result.append(maddec.MadAudioFile)
|
||||
|
||||
# FFmpeg.
|
||||
if ffdec.available():
|
||||
result.append(ffdec.FFmpegAudioFile)
|
||||
|
||||
# Cache the backends we found
|
||||
BACKENDS[:] = result
|
||||
|
||||
return BACKENDS
|
||||
|
||||
|
||||
def audio_open(path, backends=None):
|
||||
"""Open an audio file using a library that is available on this
|
||||
system.
|
||||
|
||||
The optional `backends` parameter can be a list of audio file
|
||||
classes to try opening the file with. If it is not provided,
|
||||
`audio_open` tries all available backends. If you call this function
|
||||
many times, you can avoid the cost of checking for available
|
||||
backends every time by calling `available_backends` once and passing
|
||||
the result to each `audio_open` call.
|
||||
|
||||
If all backends fail to read the file, a NoBackendError exception is
|
||||
raised.
|
||||
"""
|
||||
if backends is None:
|
||||
backends = available_backends()
|
||||
|
||||
for BackendClass in backends:
|
||||
try:
|
||||
return BackendClass(path)
|
||||
except DecodeError:
|
||||
pass
|
||||
|
||||
# All backends failed!
|
||||
raise NoBackendError()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,18 @@
|
||||
# This file is part of audioread.
|
||||
# Copyright 2021, Adrian Sampson.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
|
||||
class AudioFile:
|
||||
"""The base class for all audio file types.
|
||||
"""
|
||||
@@ -0,0 +1,25 @@
|
||||
# This file is part of audioread.
|
||||
# Copyright 2013, Adrian Sampson.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
|
||||
class DecodeError(Exception):
|
||||
"""The base exception class for all decoding errors raised by this
|
||||
package.
|
||||
"""
|
||||
|
||||
|
||||
class NoBackendError(DecodeError):
|
||||
"""The file could not be decoded by any backend. Either no backends
|
||||
are available or each available backend failed to decode the file.
|
||||
"""
|
||||
@@ -0,0 +1,320 @@
|
||||
# This file is part of audioread.
|
||||
# Copyright 2014, Adrian Sampson.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
"""Read audio data using the ffmpeg command line tool via its standard
|
||||
output.
|
||||
"""
|
||||
|
||||
import queue
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from io import DEFAULT_BUFFER_SIZE
|
||||
|
||||
from .exceptions import DecodeError
|
||||
from .base import AudioFile
|
||||
|
||||
COMMANDS = ('ffmpeg', 'avconv')
|
||||
|
||||
if sys.platform == "win32":
|
||||
PROC_FLAGS = 0x08000000
|
||||
else:
|
||||
PROC_FLAGS = 0
|
||||
|
||||
|
||||
class FFmpegError(DecodeError):
|
||||
pass
|
||||
|
||||
|
||||
class CommunicationError(FFmpegError):
|
||||
"""Raised when the output of FFmpeg is not parseable."""
|
||||
|
||||
|
||||
class UnsupportedError(FFmpegError):
|
||||
"""The file could not be decoded by FFmpeg."""
|
||||
|
||||
|
||||
class NotInstalledError(FFmpegError):
|
||||
"""Could not find the ffmpeg binary."""
|
||||
|
||||
|
||||
class ReadTimeoutError(FFmpegError):
|
||||
"""Reading from the ffmpeg command-line tool timed out."""
|
||||
|
||||
|
||||
class QueueReaderThread(threading.Thread):
|
||||
"""A thread that consumes data from a filehandle and sends the data
|
||||
over a Queue.
|
||||
"""
|
||||
def __init__(self, fh, blocksize=1024, discard=False):
|
||||
super().__init__()
|
||||
self.fh = fh
|
||||
self.blocksize = blocksize
|
||||
self.daemon = True
|
||||
self.discard = discard
|
||||
self.queue = None if discard else queue.Queue()
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
data = self.fh.read(self.blocksize)
|
||||
if not self.discard:
|
||||
self.queue.put(data)
|
||||
if not data:
|
||||
# Stream closed (EOF).
|
||||
break
|
||||
|
||||
|
||||
def popen_multiple(commands, command_args, *args, **kwargs):
|
||||
"""Like `subprocess.Popen`, but can try multiple commands in case
|
||||
some are not available.
|
||||
|
||||
`commands` is an iterable of command names and `command_args` are
|
||||
the rest of the arguments that, when appended to the command name,
|
||||
make up the full first argument to `subprocess.Popen`. The
|
||||
other positional and keyword arguments are passed through.
|
||||
"""
|
||||
for i, command in enumerate(commands):
|
||||
cmd = [command] + command_args
|
||||
try:
|
||||
return subprocess.Popen(cmd, *args, **kwargs)
|
||||
except OSError:
|
||||
if i == len(commands) - 1:
|
||||
# No more commands to try.
|
||||
raise
|
||||
|
||||
|
||||
def available():
|
||||
"""Detect whether the FFmpeg backend can be used on this system.
|
||||
"""
|
||||
try:
|
||||
proc = popen_multiple(
|
||||
COMMANDS,
|
||||
['-version'],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
creationflags=PROC_FLAGS,
|
||||
)
|
||||
except OSError:
|
||||
return False
|
||||
else:
|
||||
proc.communicate()
|
||||
return proc.returncode == 0
|
||||
|
||||
|
||||
# For Windows error switch management, we need a lock to keep the mode
|
||||
# adjustment atomic.
|
||||
windows_error_mode_lock = threading.Lock()
|
||||
|
||||
|
||||
class FFmpegAudioFile(AudioFile):
|
||||
"""An audio file decoded by the ffmpeg command-line utility."""
|
||||
def __init__(self, filename, block_size=DEFAULT_BUFFER_SIZE):
|
||||
# On Windows, we need to disable the subprocess's crash dialog
|
||||
# in case it dies. Passing SEM_NOGPFAULTERRORBOX to SetErrorMode
|
||||
# disables this behavior.
|
||||
windows = sys.platform.startswith("win")
|
||||
if windows:
|
||||
windows_error_mode_lock.acquire()
|
||||
SEM_NOGPFAULTERRORBOX = 0x0002
|
||||
import ctypes
|
||||
# We call SetErrorMode in two steps to avoid overriding
|
||||
# existing error mode.
|
||||
previous_error_mode = \
|
||||
ctypes.windll.kernel32.SetErrorMode(SEM_NOGPFAULTERRORBOX)
|
||||
ctypes.windll.kernel32.SetErrorMode(
|
||||
previous_error_mode | SEM_NOGPFAULTERRORBOX
|
||||
)
|
||||
|
||||
try:
|
||||
self.proc = popen_multiple(
|
||||
COMMANDS,
|
||||
['-i', filename, '-f', 's16le', '-'],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
stdin=subprocess.DEVNULL,
|
||||
creationflags=PROC_FLAGS,
|
||||
)
|
||||
|
||||
except OSError:
|
||||
raise NotInstalledError()
|
||||
|
||||
finally:
|
||||
# Reset previous error mode on Windows. (We can change this
|
||||
# back now because the flag was inherited by the subprocess;
|
||||
# we don't need to keep it set in the parent process.)
|
||||
if windows:
|
||||
try:
|
||||
import ctypes
|
||||
ctypes.windll.kernel32.SetErrorMode(previous_error_mode)
|
||||
finally:
|
||||
windows_error_mode_lock.release()
|
||||
|
||||
# Start another thread to consume the standard output of the
|
||||
# process, which contains raw audio data.
|
||||
self.stdout_reader = QueueReaderThread(self.proc.stdout, block_size)
|
||||
self.stdout_reader.start()
|
||||
|
||||
# Read relevant information from stderr.
|
||||
self._get_info()
|
||||
|
||||
# Start a separate thread to read the rest of the data from
|
||||
# stderr. This (a) avoids filling up the OS buffer and (b)
|
||||
# collects the error output for diagnosis.
|
||||
self.stderr_reader = QueueReaderThread(self.proc.stderr)
|
||||
self.stderr_reader.start()
|
||||
|
||||
def read_data(self, timeout=10.0):
|
||||
"""Read blocks of raw PCM data from the file."""
|
||||
# Read from stdout in a separate thread and consume data from
|
||||
# the queue.
|
||||
start_time = time.time()
|
||||
while True:
|
||||
# Wait for data to be available or a timeout.
|
||||
data = None
|
||||
try:
|
||||
data = self.stdout_reader.queue.get(timeout=timeout)
|
||||
if data:
|
||||
yield data
|
||||
else:
|
||||
# End of file.
|
||||
break
|
||||
except queue.Empty:
|
||||
# Queue read timed out.
|
||||
end_time = time.time()
|
||||
if not data:
|
||||
if end_time - start_time >= timeout:
|
||||
# Nothing interesting has happened for a while --
|
||||
# FFmpeg is probably hanging.
|
||||
raise ReadTimeoutError('ffmpeg output: {}'.format(
|
||||
b''.join(self.stderr_reader.queue.queue)
|
||||
))
|
||||
else:
|
||||
start_time = end_time
|
||||
# Keep waiting.
|
||||
continue
|
||||
|
||||
def _get_info(self):
|
||||
"""Reads the tool's output from its stderr stream, extracts the
|
||||
relevant information, and parses it.
|
||||
"""
|
||||
out_parts = []
|
||||
while True:
|
||||
line = self.proc.stderr.readline()
|
||||
if not line:
|
||||
# EOF and data not found.
|
||||
raise CommunicationError("stream info not found")
|
||||
|
||||
# In Python 3, result of reading from stderr is bytes.
|
||||
if isinstance(line, bytes):
|
||||
line = line.decode('utf8', 'ignore')
|
||||
|
||||
line = line.strip().lower()
|
||||
|
||||
if 'no such file' in line:
|
||||
raise OSError('file not found')
|
||||
elif 'invalid data found' in line:
|
||||
raise UnsupportedError()
|
||||
elif 'duration:' in line:
|
||||
out_parts.append(line)
|
||||
elif 'audio:' in line:
|
||||
out_parts.append(line)
|
||||
self._parse_info(''.join(out_parts))
|
||||
break
|
||||
|
||||
def _parse_info(self, s):
|
||||
"""Given relevant data from the ffmpeg output, set audio
|
||||
parameter fields on this object.
|
||||
"""
|
||||
# Sample rate.
|
||||
match = re.search(r'(\d+) hz', s)
|
||||
if match:
|
||||
self.samplerate = int(match.group(1))
|
||||
else:
|
||||
self.samplerate = 0
|
||||
|
||||
# Channel count.
|
||||
match = re.search(r'hz, ([^,]+),', s)
|
||||
if match:
|
||||
mode = match.group(1)
|
||||
if mode == 'stereo':
|
||||
self.channels = 2
|
||||
else:
|
||||
cmatch = re.match(r'(\d+)\.?(\d)?', mode)
|
||||
if cmatch:
|
||||
self.channels = sum(map(int, cmatch.group().split('.')))
|
||||
else:
|
||||
self.channels = 1
|
||||
else:
|
||||
self.channels = 0
|
||||
|
||||
# Duration.
|
||||
match = re.search(
|
||||
r'duration: (\d+):(\d+):(\d+).(\d)', s
|
||||
)
|
||||
if match:
|
||||
durparts = list(map(int, match.groups()))
|
||||
duration = (
|
||||
durparts[0] * 60 * 60 +
|
||||
durparts[1] * 60 +
|
||||
durparts[2] +
|
||||
float(durparts[3]) / 10
|
||||
)
|
||||
self.duration = duration
|
||||
else:
|
||||
# No duration found.
|
||||
self.duration = 0
|
||||
|
||||
def close(self):
|
||||
"""Close the ffmpeg process used to perform the decoding."""
|
||||
if hasattr(self, 'proc'):
|
||||
# First check the process's execution status before attempting to
|
||||
# kill it. This fixes an issue on Windows Subsystem for Linux where
|
||||
# ffmpeg closes normally on its own, but never updates
|
||||
# `returncode`.
|
||||
self.proc.poll()
|
||||
|
||||
# Kill the process if it is still running.
|
||||
if self.proc.returncode is None:
|
||||
self.proc.kill()
|
||||
self.proc.wait()
|
||||
|
||||
# Wait for the stream-reading threads to exit. (They need to
|
||||
# stop reading before we can close the streams.)
|
||||
if hasattr(self, 'stderr_reader'):
|
||||
self.stderr_reader.join()
|
||||
if hasattr(self, 'stdout_reader'):
|
||||
self.stdout_reader.join()
|
||||
|
||||
# Close the stdout and stderr streams that were opened by Popen,
|
||||
# which should occur regardless of if the process terminated
|
||||
# cleanly.
|
||||
self.proc.stdout.close()
|
||||
self.proc.stderr.close()
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
# Iteration.
|
||||
def __iter__(self):
|
||||
return self.read_data()
|
||||
|
||||
# Context manager.
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
return False
|
||||
@@ -0,0 +1,429 @@
|
||||
# This file is part of audioread.
|
||||
# Copyright 2011, Adrian Sampson.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
"""Use Gstreamer to decode audio files.
|
||||
|
||||
To read an audio file, pass it to the constructor for GstAudioFile()
|
||||
and then iterate over the contents:
|
||||
|
||||
>>> f = GstAudioFile('something.mp3')
|
||||
>>> try:
|
||||
>>> for block in f:
|
||||
>>> ...
|
||||
>>> finally:
|
||||
>>> f.close()
|
||||
|
||||
Note that there are a few complications caused by Gstreamer's
|
||||
asynchronous architecture. This module spawns its own Gobject main-
|
||||
loop thread; I'm not sure how that will interact with other main
|
||||
loops if your program has them. Also, in order to stop the thread
|
||||
and terminate your program normally, you need to call the close()
|
||||
method on every GstAudioFile you create. Conveniently, the file can be
|
||||
used as a context manager to make this simpler:
|
||||
|
||||
>>> with GstAudioFile('something.mp3') as f:
|
||||
>>> for block in f:
|
||||
>>> ...
|
||||
|
||||
Iterating a GstAudioFile yields strings containing short integer PCM
|
||||
data. You can also read the sample rate and channel count from the
|
||||
file:
|
||||
|
||||
>>> with GstAudioFile('something.mp3') as f:
|
||||
>>> print f.samplerate
|
||||
>>> print f.channels
|
||||
>>> print f.duration
|
||||
"""
|
||||
|
||||
import gi
|
||||
gi.require_version('Gst', '1.0')
|
||||
from gi.repository import GLib, Gst
|
||||
|
||||
import sys
|
||||
import threading
|
||||
import os
|
||||
import queue
|
||||
from urllib.parse import quote
|
||||
|
||||
from .exceptions import DecodeError
|
||||
from .base import AudioFile
|
||||
|
||||
QUEUE_SIZE = 10
|
||||
BUFFER_SIZE = 10
|
||||
SENTINEL = '__GSTDEC_SENTINEL__'
|
||||
|
||||
|
||||
# Exceptions.
|
||||
|
||||
class GStreamerError(DecodeError):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownTypeError(GStreamerError):
|
||||
"""Raised when Gstreamer can't decode the given file type."""
|
||||
def __init__(self, streaminfo):
|
||||
super().__init__(
|
||||
"can't decode stream: " + streaminfo
|
||||
)
|
||||
self.streaminfo = streaminfo
|
||||
|
||||
|
||||
class FileReadError(GStreamerError):
|
||||
"""Raised when the file can't be read at all."""
|
||||
pass
|
||||
|
||||
|
||||
class NoStreamError(GStreamerError):
|
||||
"""Raised when the file was read successfully but no audio streams
|
||||
were found.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('no audio streams found')
|
||||
|
||||
|
||||
class MetadataMissingError(GStreamerError):
|
||||
"""Raised when GStreamer fails to report stream metadata (duration,
|
||||
channels, or sample rate).
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class IncompleteGStreamerError(GStreamerError):
|
||||
"""Raised when necessary components of GStreamer (namely, the
|
||||
principal plugin packages) are missing.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
'missing GStreamer base plugins'
|
||||
)
|
||||
|
||||
|
||||
# Managing the Gobject main loop thread.
|
||||
|
||||
_shared_loop_thread = None
|
||||
_loop_thread_lock = threading.RLock()
|
||||
|
||||
Gst.init(None)
|
||||
|
||||
def get_loop_thread():
|
||||
"""Get the shared main-loop thread.
|
||||
"""
|
||||
global _shared_loop_thread
|
||||
with _loop_thread_lock:
|
||||
if not _shared_loop_thread:
|
||||
# Start a new thread.
|
||||
_shared_loop_thread = MainLoopThread()
|
||||
_shared_loop_thread.start()
|
||||
return _shared_loop_thread
|
||||
|
||||
|
||||
class MainLoopThread(threading.Thread):
|
||||
"""A daemon thread encapsulating a Gobject main loop.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.loop = GLib.MainLoop.new(None, False)
|
||||
self.daemon = True
|
||||
|
||||
def run(self):
|
||||
self.loop.run()
|
||||
|
||||
|
||||
# The decoder.
|
||||
|
||||
class GstAudioFile(AudioFile):
|
||||
"""Reads raw audio data from any audio file that Gstreamer
|
||||
knows how to decode.
|
||||
|
||||
>>> with GstAudioFile('something.mp3') as f:
|
||||
>>> print f.samplerate
|
||||
>>> print f.channels
|
||||
>>> print f.duration
|
||||
>>> for block in f:
|
||||
>>> do_something(block)
|
||||
|
||||
Iterating the object yields blocks of 16-bit PCM data. Three
|
||||
pieces of stream information are also available: samplerate (in Hz),
|
||||
number of channels, and duration (in seconds).
|
||||
|
||||
It's very important that the client call close() when it's done
|
||||
with the object. Otherwise, the program is likely to hang on exit.
|
||||
Alternatively, of course, one can just use the file as a context
|
||||
manager, as shown above.
|
||||
"""
|
||||
def __init__(self, path):
|
||||
self.running = False
|
||||
self.finished = False
|
||||
|
||||
# Set up the Gstreamer pipeline.
|
||||
self.pipeline = Gst.Pipeline()
|
||||
|
||||
self.dec = Gst.ElementFactory.make("uridecodebin", None)
|
||||
self.conv = Gst.ElementFactory.make("audioconvert", None)
|
||||
self.sink = Gst.ElementFactory.make("appsink", None)
|
||||
|
||||
if self.dec is None or self.conv is None or self.sink is None:
|
||||
# uridecodebin, audioconvert, or appsink is missing. We need
|
||||
# gst-plugins-base.
|
||||
raise IncompleteGStreamerError()
|
||||
|
||||
# Register for bus signals.
|
||||
bus = self.pipeline.get_bus()
|
||||
bus.add_signal_watch()
|
||||
bus.connect("message::eos", self._message)
|
||||
bus.connect("message::error", self._message)
|
||||
|
||||
# Configure the input.
|
||||
uri = 'file://' + quote(os.path.abspath(path))
|
||||
self.dec.set_property("uri", uri)
|
||||
# The callback to connect the input.
|
||||
self.dec.connect("pad-added", self._pad_added)
|
||||
self.dec.connect("no-more-pads", self._no_more_pads)
|
||||
# And a callback if decoding fails.
|
||||
self.dec.connect("unknown-type", self._unkown_type)
|
||||
|
||||
# Configure the output.
|
||||
# We want short integer data.
|
||||
self.sink.set_property(
|
||||
'caps',
|
||||
Gst.Caps.from_string('audio/x-raw, format=(string)S16LE'),
|
||||
)
|
||||
# TODO set endianness?
|
||||
# Set up the characteristics of the output. We don't want to
|
||||
# drop any data (nothing is real-time here); we should bound
|
||||
# the memory usage of the internal queue; and, most
|
||||
# importantly, setting "sync" to False disables the default
|
||||
# behavior in which you consume buffers in real time. This way,
|
||||
# we get data as soon as it's decoded.
|
||||
self.sink.set_property('drop', False)
|
||||
self.sink.set_property('max-buffers', BUFFER_SIZE)
|
||||
self.sink.set_property('sync', False)
|
||||
# The callback to receive decoded data.
|
||||
self.sink.set_property('emit-signals', True)
|
||||
self.sink.connect("new-sample", self._new_sample)
|
||||
|
||||
# We'll need to know when the stream becomes ready and we get
|
||||
# its attributes. This semaphore will become available when the
|
||||
# caps are received. That way, when __init__() returns, the file
|
||||
# (and its attributes) will be ready for reading.
|
||||
self.ready_sem = threading.Semaphore(0)
|
||||
self.caps_handler = self.sink.get_static_pad("sink").connect(
|
||||
"notify::caps", self._notify_caps
|
||||
)
|
||||
|
||||
# Link up everything but the decoder (which must be linked only
|
||||
# when it becomes ready).
|
||||
self.pipeline.add(self.dec)
|
||||
self.pipeline.add(self.conv)
|
||||
self.pipeline.add(self.sink)
|
||||
|
||||
self.conv.link(self.sink)
|
||||
|
||||
# Set up the queue for data and run the main thread.
|
||||
self.queue = queue.Queue(QUEUE_SIZE)
|
||||
self.thread = get_loop_thread()
|
||||
|
||||
# This wil get filled with an exception if opening fails.
|
||||
self.read_exc = None
|
||||
|
||||
# Return as soon as the stream is ready!
|
||||
self.running = True
|
||||
self.got_caps = False
|
||||
self.pipeline.set_state(Gst.State.PLAYING)
|
||||
self.ready_sem.acquire()
|
||||
if self.read_exc:
|
||||
# An error occurred before the stream became ready.
|
||||
self.close(True)
|
||||
raise self.read_exc
|
||||
|
||||
# Gstreamer callbacks.
|
||||
|
||||
def _notify_caps(self, pad, args):
|
||||
"""The callback for the sinkpad's "notify::caps" signal.
|
||||
"""
|
||||
# The sink has started to receive data, so the stream is ready.
|
||||
# This also is our opportunity to read information about the
|
||||
# stream.
|
||||
self.got_caps = True
|
||||
info = pad.get_current_caps().get_structure(0)
|
||||
|
||||
# Stream attributes.
|
||||
self.channels = info.get_int('channels')[1]
|
||||
self.samplerate = info.get_int('rate')[1]
|
||||
|
||||
# Query duration.
|
||||
success, length = pad.get_peer().query_duration(Gst.Format.TIME)
|
||||
if success:
|
||||
self.duration = length / 1000000000
|
||||
else:
|
||||
self.read_exc = MetadataMissingError('duration not available')
|
||||
|
||||
# Allow constructor to complete.
|
||||
self.ready_sem.release()
|
||||
|
||||
_got_a_pad = False
|
||||
|
||||
def _pad_added(self, element, pad):
|
||||
"""The callback for GstElement's "pad-added" signal.
|
||||
"""
|
||||
# Decoded data is ready. Connect up the decoder, finally.
|
||||
name = pad.query_caps(None).to_string()
|
||||
if name.startswith('audio/x-raw'):
|
||||
nextpad = self.conv.get_static_pad('sink')
|
||||
if not nextpad.is_linked():
|
||||
self._got_a_pad = True
|
||||
pad.link(nextpad)
|
||||
|
||||
def _no_more_pads(self, element):
|
||||
"""The callback for GstElement's "no-more-pads" signal.
|
||||
"""
|
||||
# Sent when the pads are done adding (i.e., there are no more
|
||||
# streams in the file). If we haven't gotten at least one
|
||||
# decodable stream, raise an exception.
|
||||
if not self._got_a_pad:
|
||||
self.read_exc = NoStreamError()
|
||||
self.ready_sem.release() # No effect if we've already started.
|
||||
|
||||
def _new_sample(self, sink):
|
||||
"""The callback for appsink's "new-sample" signal.
|
||||
"""
|
||||
if self.running:
|
||||
# New data is available from the pipeline! Dump it into our
|
||||
# queue (or possibly block if we're full).
|
||||
buf = sink.emit('pull-sample').get_buffer()
|
||||
|
||||
# We can't use Gst.Buffer.extract() to read the data as it crashes
|
||||
# when called through PyGObject. We also can't use
|
||||
# Gst.Buffer.extract_dup() because we have no way in Python to free
|
||||
# the memory that it returns. Instead we get access to the actual
|
||||
# data via Gst.Memory.map().
|
||||
mem = buf.get_all_memory()
|
||||
success, info = mem.map(Gst.MapFlags.READ)
|
||||
if success:
|
||||
if isinstance(info.data, memoryview):
|
||||
# We need to copy the data as the memoryview is released
|
||||
# when we call mem.unmap()
|
||||
data = bytes(info.data)
|
||||
else:
|
||||
# GStreamer Python bindings <= 1.16 return a copy of the
|
||||
# data as bytes()
|
||||
data = info.data
|
||||
mem.unmap(info)
|
||||
self.queue.put(data)
|
||||
else:
|
||||
raise GStreamerError("Unable to map buffer memory while reading the file.")
|
||||
return Gst.FlowReturn.OK
|
||||
|
||||
def _unkown_type(self, uridecodebin, decodebin, caps):
|
||||
"""The callback for decodebin's "unknown-type" signal.
|
||||
"""
|
||||
# This is called *before* the stream becomes ready when the
|
||||
# file can't be read.
|
||||
streaminfo = caps.to_string()
|
||||
if not streaminfo.startswith('audio/'):
|
||||
# Ignore non-audio (e.g., video) decode errors.
|
||||
return
|
||||
self.read_exc = UnknownTypeError(streaminfo)
|
||||
self.ready_sem.release()
|
||||
|
||||
def _message(self, bus, message):
|
||||
"""The callback for GstBus's "message" signal (for two kinds of
|
||||
messages).
|
||||
"""
|
||||
if not self.finished:
|
||||
if message.type == Gst.MessageType.EOS:
|
||||
# The file is done. Tell the consumer thread.
|
||||
self.queue.put(SENTINEL)
|
||||
if not self.got_caps:
|
||||
# If the stream ends before _notify_caps was called, this
|
||||
# is an invalid file.
|
||||
self.read_exc = NoStreamError()
|
||||
self.ready_sem.release()
|
||||
|
||||
elif message.type == Gst.MessageType.ERROR:
|
||||
gerror, debug = message.parse_error()
|
||||
if 'not-linked' in debug:
|
||||
self.read_exc = NoStreamError()
|
||||
elif 'No such file' in debug:
|
||||
self.read_exc = IOError('resource not found')
|
||||
else:
|
||||
self.read_exc = FileReadError(debug)
|
||||
self.ready_sem.release()
|
||||
|
||||
# Iteration.
|
||||
|
||||
def __next__(self):
|
||||
# Wait for data from the Gstreamer callbacks.
|
||||
val = self.queue.get()
|
||||
if val == SENTINEL:
|
||||
# End of stream.
|
||||
raise StopIteration
|
||||
return val
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
# Cleanup.
|
||||
def close(self, force=False):
|
||||
"""Close the file and clean up associated resources.
|
||||
|
||||
Calling `close()` a second time has no effect.
|
||||
"""
|
||||
if self.running or force:
|
||||
self.running = False
|
||||
self.finished = True
|
||||
|
||||
# Unregister for signals, which we registered for above with
|
||||
# `add_signal_watch`. (Without this, GStreamer leaks file
|
||||
# descriptors.)
|
||||
self.pipeline.get_bus().remove_signal_watch()
|
||||
|
||||
# Stop reading the file.
|
||||
self.dec.set_property("uri", None)
|
||||
# Block spurious signals.
|
||||
self.sink.get_static_pad("sink").disconnect(self.caps_handler)
|
||||
|
||||
# Make space in the output queue to let the decoder thread
|
||||
# finish. (Otherwise, the thread blocks on its enqueue and
|
||||
# the interpreter hangs.)
|
||||
try:
|
||||
self.queue.get_nowait()
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
# Halt the pipeline (closing file).
|
||||
self.pipeline.set_state(Gst.State.NULL)
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
# Context manager.
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
return False
|
||||
|
||||
|
||||
# Smoke test.
|
||||
if __name__ == '__main__':
|
||||
for path in sys.argv[1:]:
|
||||
path = os.path.abspath(os.path.expanduser(path))
|
||||
with GstAudioFile(path) as f:
|
||||
print(f.channels)
|
||||
print(f.samplerate)
|
||||
print(f.duration)
|
||||
for s in f:
|
||||
print(len(s), ord(s[0]))
|
||||
@@ -0,0 +1,348 @@
|
||||
# This file is part of audioread.
|
||||
# Copyright 2011, Adrian Sampson.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
"""Read audio files using CoreAudio on Mac OS X."""
|
||||
import copy
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
import os
|
||||
import sys
|
||||
|
||||
from .exceptions import DecodeError
|
||||
from .base import AudioFile
|
||||
|
||||
|
||||
# CoreFoundation and CoreAudio libraries along with their function
|
||||
# prototypes.
|
||||
|
||||
def _load_framework(name):
|
||||
return ctypes.cdll.LoadLibrary(ctypes.util.find_library(name))
|
||||
|
||||
|
||||
_coreaudio = _load_framework('AudioToolbox')
|
||||
_corefoundation = _load_framework('CoreFoundation')
|
||||
|
||||
# Convert CFStrings to C strings.
|
||||
_corefoundation.CFStringGetCStringPtr.restype = ctypes.c_char_p
|
||||
_corefoundation.CFStringGetCStringPtr.argtypes = [ctypes.c_void_p,
|
||||
ctypes.c_int]
|
||||
|
||||
# Free memory.
|
||||
_corefoundation.CFRelease.argtypes = [ctypes.c_void_p]
|
||||
|
||||
# Create a file:// URL.
|
||||
_corefoundation.CFURLCreateFromFileSystemRepresentation.restype = \
|
||||
ctypes.c_void_p
|
||||
_corefoundation.CFURLCreateFromFileSystemRepresentation.argtypes = \
|
||||
[ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_bool]
|
||||
|
||||
# Get a string representation of a URL.
|
||||
_corefoundation.CFURLGetString.restype = ctypes.c_void_p
|
||||
_corefoundation.CFURLGetString.argtypes = [ctypes.c_void_p]
|
||||
|
||||
# Open an audio file for reading.
|
||||
_coreaudio.ExtAudioFileOpenURL.restype = ctypes.c_int
|
||||
_coreaudio.ExtAudioFileOpenURL.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
|
||||
|
||||
# Set audio file property.
|
||||
_coreaudio.ExtAudioFileSetProperty.restype = ctypes.c_int
|
||||
_coreaudio.ExtAudioFileSetProperty.argtypes = \
|
||||
[ctypes.c_void_p, ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p]
|
||||
|
||||
# Get audio file property.
|
||||
_coreaudio.ExtAudioFileGetProperty.restype = ctypes.c_int
|
||||
_coreaudio.ExtAudioFileGetProperty.argtypes = \
|
||||
[ctypes.c_void_p, ctypes.c_uint, ctypes.c_void_p, ctypes.c_void_p]
|
||||
|
||||
# Read from an audio file.
|
||||
_coreaudio.ExtAudioFileRead.restype = ctypes.c_int
|
||||
_coreaudio.ExtAudioFileRead.argtypes = \
|
||||
[ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
|
||||
|
||||
# Close/free an audio file.
|
||||
_coreaudio.ExtAudioFileDispose.restype = ctypes.c_int
|
||||
_coreaudio.ExtAudioFileDispose.argtypes = [ctypes.c_void_p]
|
||||
|
||||
|
||||
# Constants used in CoreAudio.
|
||||
|
||||
def multi_char_literal(chars):
|
||||
"""Emulates character integer literals in C. Given a string "abc",
|
||||
returns the value of the C single-quoted literal 'abc'.
|
||||
"""
|
||||
num = 0
|
||||
for index, char in enumerate(chars):
|
||||
shift = (len(chars) - index - 1) * 8
|
||||
num |= ord(char) << shift
|
||||
return num
|
||||
|
||||
|
||||
PROP_FILE_DATA_FORMAT = multi_char_literal('ffmt')
|
||||
PROP_CLIENT_DATA_FORMAT = multi_char_literal('cfmt')
|
||||
PROP_LENGTH = multi_char_literal('#frm')
|
||||
AUDIO_ID_PCM = multi_char_literal('lpcm')
|
||||
PCM_IS_FLOAT = 1 << 0
|
||||
PCM_IS_BIG_ENDIAN = 1 << 1
|
||||
PCM_IS_SIGNED_INT = 1 << 2
|
||||
PCM_IS_PACKED = 1 << 3
|
||||
ERROR_TYPE = multi_char_literal('typ?')
|
||||
ERROR_FORMAT = multi_char_literal('fmt?')
|
||||
ERROR_NOT_FOUND = -43
|
||||
|
||||
|
||||
# Check for errors in functions that return error codes.
|
||||
|
||||
class MacError(DecodeError):
|
||||
def __init__(self, code):
|
||||
if code == ERROR_TYPE:
|
||||
msg = 'unsupported audio type'
|
||||
elif code == ERROR_FORMAT:
|
||||
msg = 'unsupported format'
|
||||
else:
|
||||
msg = 'error %i' % code
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
def check(err):
|
||||
"""If err is nonzero, raise a MacError exception."""
|
||||
if err == ERROR_NOT_FOUND:
|
||||
raise OSError('file not found')
|
||||
elif err != 0:
|
||||
raise MacError(err)
|
||||
|
||||
|
||||
# CoreFoundation objects.
|
||||
|
||||
class CFObject:
|
||||
def __init__(self, obj):
|
||||
if obj == 0:
|
||||
raise ValueError('object is zero')
|
||||
self._obj = obj
|
||||
|
||||
def __del__(self):
|
||||
if _corefoundation:
|
||||
_corefoundation.CFRelease(self._obj)
|
||||
|
||||
|
||||
class CFURL(CFObject):
|
||||
def __init__(self, filename):
|
||||
filename = os.path.abspath(os.path.expanduser(filename))
|
||||
if not isinstance(filename, bytes):
|
||||
filename = filename.encode(sys.getfilesystemencoding())
|
||||
url = _corefoundation.CFURLCreateFromFileSystemRepresentation(
|
||||
0, filename, len(filename), False
|
||||
)
|
||||
super().__init__(url)
|
||||
|
||||
def __str__(self):
|
||||
cfstr = _corefoundation.CFURLGetString(self._obj)
|
||||
out = _corefoundation.CFStringGetCStringPtr(cfstr, 0)
|
||||
# Resulting CFString does not need to be released according to docs.
|
||||
return out
|
||||
|
||||
|
||||
# Structs used in CoreAudio.
|
||||
|
||||
class AudioStreamBasicDescription(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("mSampleRate", ctypes.c_double),
|
||||
("mFormatID", ctypes.c_uint),
|
||||
("mFormatFlags", ctypes.c_uint),
|
||||
("mBytesPerPacket", ctypes.c_uint),
|
||||
("mFramesPerPacket", ctypes.c_uint),
|
||||
("mBytesPerFrame", ctypes.c_uint),
|
||||
("mChannelsPerFrame", ctypes.c_uint),
|
||||
("mBitsPerChannel", ctypes.c_uint),
|
||||
("mReserved", ctypes.c_uint),
|
||||
]
|
||||
|
||||
|
||||
class AudioBuffer(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("mNumberChannels", ctypes.c_uint),
|
||||
("mDataByteSize", ctypes.c_uint),
|
||||
("mData", ctypes.c_void_p),
|
||||
]
|
||||
|
||||
|
||||
class AudioBufferList(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("mNumberBuffers", ctypes.c_uint),
|
||||
("mBuffers", AudioBuffer * 1),
|
||||
]
|
||||
|
||||
|
||||
# Main functionality.
|
||||
|
||||
class ExtAudioFile(AudioFile):
|
||||
"""A CoreAudio "extended audio file". Reads information and raw PCM
|
||||
audio data from any file that CoreAudio knows how to decode.
|
||||
|
||||
>>> with ExtAudioFile('something.m4a') as f:
|
||||
>>> print f.samplerate
|
||||
>>> print f.channels
|
||||
>>> print f.duration
|
||||
>>> for block in f:
|
||||
>>> do_something(block)
|
||||
|
||||
"""
|
||||
def __init__(self, filename):
|
||||
url = CFURL(filename)
|
||||
try:
|
||||
self._obj = self._open_url(url)
|
||||
except:
|
||||
self.closed = True
|
||||
raise
|
||||
del url
|
||||
|
||||
self.closed = False
|
||||
self._file_fmt = None
|
||||
self._client_fmt = None
|
||||
|
||||
self.setup()
|
||||
|
||||
@classmethod
|
||||
def _open_url(cls, url):
|
||||
"""Given a CFURL Python object, return an opened ExtAudioFileRef.
|
||||
"""
|
||||
file_obj = ctypes.c_void_p()
|
||||
check(_coreaudio.ExtAudioFileOpenURL(
|
||||
url._obj, ctypes.byref(file_obj)
|
||||
))
|
||||
return file_obj
|
||||
|
||||
def set_client_format(self, desc):
|
||||
"""Get the client format description. This describes the
|
||||
encoding of the data that the program will read from this
|
||||
object.
|
||||
"""
|
||||
assert desc.mFormatID == AUDIO_ID_PCM
|
||||
check(_coreaudio.ExtAudioFileSetProperty(
|
||||
self._obj, PROP_CLIENT_DATA_FORMAT, ctypes.sizeof(desc),
|
||||
ctypes.byref(desc)
|
||||
))
|
||||
self._client_fmt = desc
|
||||
|
||||
def get_file_format(self):
|
||||
"""Get the file format description. This describes the type of
|
||||
data stored on disk.
|
||||
"""
|
||||
# Have cached file format?
|
||||
if self._file_fmt is not None:
|
||||
return self._file_fmt
|
||||
|
||||
# Make the call to retrieve it.
|
||||
desc = AudioStreamBasicDescription()
|
||||
size = ctypes.c_int(ctypes.sizeof(desc))
|
||||
check(_coreaudio.ExtAudioFileGetProperty(
|
||||
self._obj, PROP_FILE_DATA_FORMAT, ctypes.byref(size),
|
||||
ctypes.byref(desc)
|
||||
))
|
||||
|
||||
# Cache result.
|
||||
self._file_fmt = desc
|
||||
return desc
|
||||
|
||||
@property
|
||||
def channels(self):
|
||||
"""The number of channels in the audio source."""
|
||||
return int(self.get_file_format().mChannelsPerFrame)
|
||||
|
||||
@property
|
||||
def samplerate(self):
|
||||
"""Gets the sample rate of the audio."""
|
||||
return int(self.get_file_format().mSampleRate)
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
"""Gets the length of the file in seconds (a float)."""
|
||||
return float(self.nframes) / self.samplerate
|
||||
|
||||
@property
|
||||
def nframes(self):
|
||||
"""Gets the number of frames in the source file."""
|
||||
length = ctypes.c_long()
|
||||
size = ctypes.c_int(ctypes.sizeof(length))
|
||||
check(_coreaudio.ExtAudioFileGetProperty(
|
||||
self._obj, PROP_LENGTH, ctypes.byref(size), ctypes.byref(length)
|
||||
))
|
||||
return length.value
|
||||
|
||||
def setup(self, bitdepth=16):
|
||||
"""Set the client format parameters, specifying the desired PCM
|
||||
audio data format to be read from the file. Must be called
|
||||
before reading from the file.
|
||||
"""
|
||||
fmt = self.get_file_format()
|
||||
newfmt = copy.copy(fmt)
|
||||
|
||||
newfmt.mFormatID = AUDIO_ID_PCM
|
||||
newfmt.mFormatFlags = \
|
||||
PCM_IS_SIGNED_INT | PCM_IS_PACKED
|
||||
newfmt.mBitsPerChannel = bitdepth
|
||||
newfmt.mBytesPerPacket = \
|
||||
(fmt.mChannelsPerFrame * newfmt.mBitsPerChannel // 8)
|
||||
newfmt.mFramesPerPacket = 1
|
||||
newfmt.mBytesPerFrame = newfmt.mBytesPerPacket
|
||||
self.set_client_format(newfmt)
|
||||
|
||||
def read_data(self, blocksize=4096):
|
||||
"""Generates byte strings reflecting the audio data in the file.
|
||||
"""
|
||||
frames = ctypes.c_uint(blocksize // self._client_fmt.mBytesPerFrame)
|
||||
buf = ctypes.create_string_buffer(blocksize)
|
||||
|
||||
buflist = AudioBufferList()
|
||||
buflist.mNumberBuffers = 1
|
||||
buflist.mBuffers[0].mNumberChannels = \
|
||||
self._client_fmt.mChannelsPerFrame
|
||||
buflist.mBuffers[0].mDataByteSize = blocksize
|
||||
buflist.mBuffers[0].mData = ctypes.cast(buf, ctypes.c_void_p)
|
||||
|
||||
while True:
|
||||
check(_coreaudio.ExtAudioFileRead(
|
||||
self._obj, ctypes.byref(frames), ctypes.byref(buflist)
|
||||
))
|
||||
|
||||
assert buflist.mNumberBuffers == 1
|
||||
size = buflist.mBuffers[0].mDataByteSize
|
||||
if not size:
|
||||
break
|
||||
|
||||
data = ctypes.cast(buflist.mBuffers[0].mData,
|
||||
ctypes.POINTER(ctypes.c_char))
|
||||
blob = data[:size]
|
||||
yield blob
|
||||
|
||||
def close(self):
|
||||
"""Close the audio file and free associated memory."""
|
||||
if not self.closed:
|
||||
check(_coreaudio.ExtAudioFileDispose(self._obj))
|
||||
self.closed = True
|
||||
|
||||
def __del__(self):
|
||||
if _coreaudio:
|
||||
self.close()
|
||||
|
||||
# Context manager methods.
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
return False
|
||||
|
||||
# Iteration.
|
||||
def __iter__(self):
|
||||
return self.read_data()
|
||||
@@ -0,0 +1,86 @@
|
||||
# This file is part of audioread.
|
||||
# Copyright 2011, Adrian Sampson.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
"""Decode MPEG audio files with MAD (via pymad)."""
|
||||
import mad
|
||||
|
||||
from . import DecodeError
|
||||
from .base import AudioFile
|
||||
|
||||
|
||||
class UnsupportedError(DecodeError):
|
||||
"""The file is not readable by MAD."""
|
||||
|
||||
|
||||
class MadAudioFile(AudioFile):
|
||||
"""MPEG audio file decoder using the MAD library."""
|
||||
def __init__(self, filename):
|
||||
self.fp = open(filename, 'rb')
|
||||
self.mf = mad.MadFile(self.fp)
|
||||
if not self.mf.total_time(): # Indicates a failed open.
|
||||
self.fp.close()
|
||||
raise UnsupportedError()
|
||||
|
||||
def close(self):
|
||||
if hasattr(self, 'fp'):
|
||||
self.fp.close()
|
||||
if hasattr(self, 'mf'):
|
||||
del self.mf
|
||||
|
||||
def read_blocks(self, block_size=4096):
|
||||
"""Generates buffers containing PCM data for the audio file.
|
||||
"""
|
||||
while True:
|
||||
out = self.mf.read(block_size)
|
||||
if not out:
|
||||
break
|
||||
yield bytes(out)
|
||||
|
||||
@property
|
||||
def samplerate(self):
|
||||
"""Sample rate in Hz."""
|
||||
return self.mf.samplerate()
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
"""Length of the audio in seconds (a float)."""
|
||||
return float(self.mf.total_time()) / 1000
|
||||
|
||||
@property
|
||||
def channels(self):
|
||||
"""The number of channels."""
|
||||
if self.mf.mode() == mad.MODE_SINGLE_CHANNEL:
|
||||
return 1
|
||||
elif self.mf.mode() in (mad.MODE_DUAL_CHANNEL,
|
||||
mad.MODE_JOINT_STEREO,
|
||||
mad.MODE_STEREO):
|
||||
return 2
|
||||
else:
|
||||
# Other mode?
|
||||
return 2
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
# Iteration.
|
||||
def __iter__(self):
|
||||
return self.read_blocks()
|
||||
|
||||
# Context manager.
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
return False
|
||||
@@ -0,0 +1,149 @@
|
||||
# This file is part of audioread.
|
||||
# Copyright 2011, Adrian Sampson.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
"""Uses standard-library modules to read AIFF, AIFF-C, and WAV files."""
|
||||
import aifc
|
||||
import audioop
|
||||
import struct
|
||||
import sunau
|
||||
import wave
|
||||
|
||||
from .exceptions import DecodeError
|
||||
from .base import AudioFile
|
||||
|
||||
# Produce two-byte (16-bit) output samples.
|
||||
TARGET_WIDTH = 2
|
||||
|
||||
# Python 3.4 added support for 24-bit (3-byte) samples.
|
||||
SUPPORTED_WIDTHS = (1, 2, 3, 4)
|
||||
|
||||
|
||||
class UnsupportedError(DecodeError):
|
||||
"""File is not an AIFF, WAV, or Au file."""
|
||||
|
||||
|
||||
class BitWidthError(DecodeError):
|
||||
"""The file uses an unsupported bit width."""
|
||||
|
||||
|
||||
def byteswap(s):
|
||||
"""Swaps the endianness of the bytestring s, which must be an array
|
||||
of shorts (16-bit signed integers). This is probably less efficient
|
||||
than it should be.
|
||||
"""
|
||||
assert len(s) % 2 == 0
|
||||
parts = []
|
||||
for i in range(0, len(s), 2):
|
||||
chunk = s[i:i + 2]
|
||||
newchunk = struct.pack('<h', *struct.unpack('>h', chunk))
|
||||
parts.append(newchunk)
|
||||
return b''.join(parts)
|
||||
|
||||
|
||||
class RawAudioFile(AudioFile):
|
||||
"""An AIFF, WAV, or Au file that can be read by the Python standard
|
||||
library modules ``wave``, ``aifc``, and ``sunau``.
|
||||
"""
|
||||
def __init__(self, filename):
|
||||
self._fh = open(filename, 'rb')
|
||||
|
||||
try:
|
||||
self._file = aifc.open(self._fh)
|
||||
except aifc.Error:
|
||||
# Return to the beginning of the file to try the next reader.
|
||||
self._fh.seek(0)
|
||||
else:
|
||||
self._needs_byteswap = True
|
||||
self._check()
|
||||
return
|
||||
|
||||
try:
|
||||
self._file = wave.open(self._fh)
|
||||
except wave.Error:
|
||||
self._fh.seek(0)
|
||||
pass
|
||||
else:
|
||||
self._needs_byteswap = False
|
||||
self._check()
|
||||
return
|
||||
|
||||
try:
|
||||
self._file = sunau.open(self._fh)
|
||||
except sunau.Error:
|
||||
self._fh.seek(0)
|
||||
pass
|
||||
else:
|
||||
self._needs_byteswap = True
|
||||
self._check()
|
||||
return
|
||||
|
||||
# None of the three libraries could open the file.
|
||||
self._fh.close()
|
||||
raise UnsupportedError()
|
||||
|
||||
def _check(self):
|
||||
"""Check that the files' parameters allow us to decode it and
|
||||
raise an error otherwise.
|
||||
"""
|
||||
if self._file.getsampwidth() not in SUPPORTED_WIDTHS:
|
||||
self.close()
|
||||
raise BitWidthError()
|
||||
|
||||
def close(self):
|
||||
"""Close the underlying file."""
|
||||
self._file.close()
|
||||
self._fh.close()
|
||||
|
||||
@property
|
||||
def channels(self):
|
||||
"""Number of audio channels."""
|
||||
return self._file.getnchannels()
|
||||
|
||||
@property
|
||||
def samplerate(self):
|
||||
"""Sample rate in Hz."""
|
||||
return self._file.getframerate()
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
"""Length of the audio in seconds (a float)."""
|
||||
return float(self._file.getnframes()) / self.samplerate
|
||||
|
||||
def read_data(self, block_samples=1024):
|
||||
"""Generates blocks of PCM data found in the file."""
|
||||
old_width = self._file.getsampwidth()
|
||||
|
||||
while True:
|
||||
data = self._file.readframes(block_samples)
|
||||
if not data:
|
||||
break
|
||||
|
||||
# Make sure we have the desired bitdepth and endianness.
|
||||
data = audioop.lin2lin(data, old_width, TARGET_WIDTH)
|
||||
if self._needs_byteswap and self._file.getcomptype() != 'sowt':
|
||||
# Big-endian data. Swap endianness.
|
||||
data = byteswap(data)
|
||||
yield data
|
||||
|
||||
# Context manager.
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
return False
|
||||
|
||||
# Iteration.
|
||||
def __iter__(self):
|
||||
return self.read_data()
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -0,0 +1,78 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: certifi
|
||||
Version: 2026.2.25
|
||||
Summary: Python package for providing Mozilla's CA Bundle.
|
||||
Home-page: https://github.com/certifi/python-certifi
|
||||
Author: Kenneth Reitz
|
||||
Author-email: me@kennethreitz.com
|
||||
License: MPL-2.0
|
||||
Project-URL: Source, https://github.com/certifi/python-certifi
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
|
||||
Classifier: Natural Language :: English
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3 :: Only
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: 3.13
|
||||
Classifier: Programming Language :: Python :: 3.14
|
||||
Requires-Python: >=3.7
|
||||
License-File: LICENSE
|
||||
Dynamic: author
|
||||
Dynamic: author-email
|
||||
Dynamic: classifier
|
||||
Dynamic: description
|
||||
Dynamic: home-page
|
||||
Dynamic: license
|
||||
Dynamic: license-file
|
||||
Dynamic: project-url
|
||||
Dynamic: requires-python
|
||||
Dynamic: summary
|
||||
|
||||
Certifi: Python SSL Certificates
|
||||
================================
|
||||
|
||||
Certifi provides Mozilla's carefully curated collection of Root Certificates for
|
||||
validating the trustworthiness of SSL certificates while verifying the identity
|
||||
of TLS hosts. It has been extracted from the `Requests`_ project.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
``certifi`` is available on PyPI. Simply install it with ``pip``::
|
||||
|
||||
$ pip install certifi
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
To reference the installed certificate authority (CA) bundle, you can use the
|
||||
built-in function::
|
||||
|
||||
>>> import certifi
|
||||
|
||||
>>> certifi.where()
|
||||
'/usr/local/lib/python3.7/site-packages/certifi/cacert.pem'
|
||||
|
||||
Or from the command line::
|
||||
|
||||
$ python -m certifi
|
||||
/usr/local/lib/python3.7/site-packages/certifi/cacert.pem
|
||||
|
||||
Enjoy!
|
||||
|
||||
.. _`Requests`: https://requests.readthedocs.io/en/master/
|
||||
|
||||
Addition/Removal of Certificates
|
||||
--------------------------------
|
||||
|
||||
Certifi does not support any addition/removal or other modification of the
|
||||
CA trust store content. This project is intended to provide a reliable and
|
||||
highly portable root of trust to python deployments. Look to upstream projects
|
||||
for methods to use alternate trust.
|
||||
@@ -0,0 +1,14 @@
|
||||
certifi-2026.2.25.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
certifi-2026.2.25.dist-info/METADATA,sha256=4NMuGXdg_hBiRA3paKVXYcDmE3VXEBWxTvCL2xlDyPU,2474
|
||||
certifi-2026.2.25.dist-info/RECORD,,
|
||||
certifi-2026.2.25.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
||||
certifi-2026.2.25.dist-info/licenses/LICENSE,sha256=6TcW2mucDVpKHfYP5pWzcPBpVgPSH2-D8FPkLPwQyvc,989
|
||||
certifi-2026.2.25.dist-info/top_level.txt,sha256=KMu4vUCfsjLrkPbSNdgdekS-pVJzBAJFO__nI8NF6-U,8
|
||||
certifi/__init__.py,sha256=c9eaYufv1pSLl0Q8QNcMiMLLH4WquDcxdPyKjmI4opY,94
|
||||
certifi/__main__.py,sha256=xBBoj905TUWBLRGANOcf7oi6e-3dMP4cEoG9OyMs11g,243
|
||||
certifi/__pycache__/__init__.cpython-312.pyc,,
|
||||
certifi/__pycache__/__main__.cpython-312.pyc,,
|
||||
certifi/__pycache__/core.cpython-312.pyc,,
|
||||
certifi/cacert.pem,sha256=_JFloSQDJj5-v72te-ej6sD6XTJdPHBGXyjTaQByyig,272441
|
||||
certifi/core.py,sha256=XFXycndG5pf37ayeF8N32HUuDafsyhkVMbO4BAPWHa0,3394
|
||||
certifi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
@@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: setuptools (82.0.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
This package contains a modified version of ca-bundle.crt:
|
||||
|
||||
ca-bundle.crt -- Bundle of CA Root Certificates
|
||||
|
||||
This is a bundle of X.509 certificates of public Certificate Authorities
|
||||
(CA). These were automatically extracted from Mozilla's root certificates
|
||||
file (certdata.txt). This file can be found in the mozilla source tree:
|
||||
https://hg.mozilla.org/mozilla-central/file/tip/security/nss/lib/ckfw/builtins/certdata.txt
|
||||
It contains the certificates in PEM format and therefore
|
||||
can be directly used with curl / libcurl / php_curl, or with
|
||||
an Apache+mod_ssl webserver for SSL client authentication.
|
||||
Just configure this file as the SSLCACertificateFile.#
|
||||
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||
v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain
|
||||
one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
@(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $
|
||||
@@ -0,0 +1 @@
|
||||
certifi
|
||||
@@ -0,0 +1,4 @@
|
||||
from .core import contents, where
|
||||
|
||||
__all__ = ["contents", "where"]
|
||||
__version__ = "2026.02.25"
|
||||
@@ -0,0 +1,12 @@
|
||||
import argparse
|
||||
|
||||
from certifi import contents, where
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-c", "--contents", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.contents:
|
||||
print(contents())
|
||||
else:
|
||||
print(where())
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
4494
linedance-app/venv/lib/python3.12/site-packages/certifi/cacert.pem
Normal file
4494
linedance-app/venv/lib/python3.12/site-packages/certifi/cacert.pem
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,83 @@
|
||||
"""
|
||||
certifi.py
|
||||
~~~~~~~~~~
|
||||
|
||||
This module returns the installation location of cacert.pem or its contents.
|
||||
"""
|
||||
import sys
|
||||
import atexit
|
||||
|
||||
def exit_cacert_ctx() -> None:
|
||||
_CACERT_CTX.__exit__(None, None, None) # type: ignore[union-attr]
|
||||
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
|
||||
from importlib.resources import as_file, files
|
||||
|
||||
_CACERT_CTX = None
|
||||
_CACERT_PATH = None
|
||||
|
||||
def where() -> str:
|
||||
# This is slightly terrible, but we want to delay extracting the file
|
||||
# in cases where we're inside of a zipimport situation until someone
|
||||
# actually calls where(), but we don't want to re-extract the file
|
||||
# on every call of where(), so we'll do it once then store it in a
|
||||
# global variable.
|
||||
global _CACERT_CTX
|
||||
global _CACERT_PATH
|
||||
if _CACERT_PATH is None:
|
||||
# This is slightly janky, the importlib.resources API wants you to
|
||||
# manage the cleanup of this file, so it doesn't actually return a
|
||||
# path, it returns a context manager that will give you the path
|
||||
# when you enter it and will do any cleanup when you leave it. In
|
||||
# the common case of not needing a temporary file, it will just
|
||||
# return the file system location and the __exit__() is a no-op.
|
||||
#
|
||||
# We also have to hold onto the actual context manager, because
|
||||
# it will do the cleanup whenever it gets garbage collected, so
|
||||
# we will also store that at the global level as well.
|
||||
_CACERT_CTX = as_file(files("certifi").joinpath("cacert.pem"))
|
||||
_CACERT_PATH = str(_CACERT_CTX.__enter__())
|
||||
atexit.register(exit_cacert_ctx)
|
||||
|
||||
return _CACERT_PATH
|
||||
|
||||
def contents() -> str:
|
||||
return files("certifi").joinpath("cacert.pem").read_text(encoding="ascii")
|
||||
|
||||
else:
|
||||
|
||||
from importlib.resources import path as get_path, read_text
|
||||
|
||||
_CACERT_CTX = None
|
||||
_CACERT_PATH = None
|
||||
|
||||
def where() -> str:
|
||||
# This is slightly terrible, but we want to delay extracting the
|
||||
# file in cases where we're inside of a zipimport situation until
|
||||
# someone actually calls where(), but we don't want to re-extract
|
||||
# the file on every call of where(), so we'll do it once then store
|
||||
# it in a global variable.
|
||||
global _CACERT_CTX
|
||||
global _CACERT_PATH
|
||||
if _CACERT_PATH is None:
|
||||
# This is slightly janky, the importlib.resources API wants you
|
||||
# to manage the cleanup of this file, so it doesn't actually
|
||||
# return a path, it returns a context manager that will give
|
||||
# you the path when you enter it and will do any cleanup when
|
||||
# you leave it. In the common case of not needing a temporary
|
||||
# file, it will just return the file system location and the
|
||||
# __exit__() is a no-op.
|
||||
#
|
||||
# We also have to hold onto the actual context manager, because
|
||||
# it will do the cleanup whenever it gets garbage collected, so
|
||||
# we will also store that at the global level as well.
|
||||
_CACERT_CTX = get_path("certifi", "cacert.pem")
|
||||
_CACERT_PATH = str(_CACERT_CTX.__enter__())
|
||||
atexit.register(exit_cacert_ctx)
|
||||
|
||||
return _CACERT_PATH
|
||||
|
||||
def contents() -> str:
|
||||
return read_text("certifi", "cacert.pem", encoding="ascii")
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -0,0 +1,68 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: cffi
|
||||
Version: 2.0.0
|
||||
Summary: Foreign Function Interface for Python calling C code.
|
||||
Author: Armin Rigo, Maciej Fijalkowski
|
||||
Maintainer: Matt Davis, Matt Clay, Matti Picus
|
||||
License-Expression: MIT
|
||||
Project-URL: Documentation, https://cffi.readthedocs.io/
|
||||
Project-URL: Changelog, https://cffi.readthedocs.io/en/latest/whatsnew.html
|
||||
Project-URL: Downloads, https://github.com/python-cffi/cffi/releases
|
||||
Project-URL: Contact, https://groups.google.com/forum/#!forum/python-cffi
|
||||
Project-URL: Source Code, https://github.com/python-cffi/cffi
|
||||
Project-URL: Issue Tracker, https://github.com/python-cffi/cffi/issues
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: 3.13
|
||||
Classifier: Programming Language :: Python :: 3.14
|
||||
Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Requires-Python: >=3.9
|
||||
Description-Content-Type: text/markdown
|
||||
License-File: LICENSE
|
||||
License-File: AUTHORS
|
||||
Requires-Dist: pycparser; implementation_name != "PyPy"
|
||||
Dynamic: license-file
|
||||
|
||||
[](https://github.com/python-cffi/cffi/actions/workflows/ci.yaml?query=branch%3Amain++)
|
||||
[](https://pypi.org/project/cffi)
|
||||
[][Documentation]
|
||||
|
||||
|
||||
CFFI
|
||||
====
|
||||
|
||||
Foreign Function Interface for Python calling C code.
|
||||
|
||||
Please see the [Documentation] or uncompiled in the `doc/` subdirectory.
|
||||
|
||||
Download
|
||||
--------
|
||||
|
||||
[Download page](https://github.com/python-cffi/cffi/releases)
|
||||
|
||||
Source Code
|
||||
-----------
|
||||
|
||||
Source code is publicly available on
|
||||
[GitHub](https://github.com/python-cffi/cffi).
|
||||
|
||||
Contact
|
||||
-------
|
||||
|
||||
[Mailing list](https://groups.google.com/forum/#!forum/python-cffi)
|
||||
|
||||
Testing/development tips
|
||||
------------------------
|
||||
|
||||
After `git clone` or `wget && tar`, we will get a directory called `cffi` or `cffi-x.x.x`. we call it `repo-directory`. To run tests under CPython, run the following in the `repo-directory`:
|
||||
|
||||
pip install pytest
|
||||
pip install -e . # editable install of CFFI for local development
|
||||
pytest src/c/ testing/
|
||||
|
||||
[Documentation]: http://cffi.readthedocs.org/
|
||||
@@ -0,0 +1,49 @@
|
||||
_cffi_backend.cpython-312-x86_64-linux-gnu.so,sha256=AGLtw5fn9u4Cmwk3BbGlsXG7VZEvQekABMyEGuRZmcE,348808
|
||||
cffi-2.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
cffi-2.0.0.dist-info/METADATA,sha256=uYzn40F68Im8EtXHNBLZs7FoPM-OxzyYbDWsjJvhujk,2559
|
||||
cffi-2.0.0.dist-info/RECORD,,
|
||||
cffi-2.0.0.dist-info/WHEEL,sha256=aSgG0F4rGPZtV0iTEIfy6dtHq6g67Lze3uLfk0vWn88,151
|
||||
cffi-2.0.0.dist-info/entry_points.txt,sha256=y6jTxnyeuLnL-XJcDv8uML3n6wyYiGRg8MTp_QGJ9Ho,75
|
||||
cffi-2.0.0.dist-info/licenses/AUTHORS,sha256=KmemC7-zN1nWfWRf8TG45ta8TK_CMtdR_Kw-2k0xTMg,208
|
||||
cffi-2.0.0.dist-info/licenses/LICENSE,sha256=W6JN3FcGf5JJrdZEw6_EGl1tw34jQz73Wdld83Cwr2M,1123
|
||||
cffi-2.0.0.dist-info/top_level.txt,sha256=rE7WR3rZfNKxWI9-jn6hsHCAl7MDkB-FmuQbxWjFehQ,19
|
||||
cffi/__init__.py,sha256=-ksBQ7MfDzVvbBlV_ftYBWAmEqfA86ljIzMxzaZeAlI,511
|
||||
cffi/__pycache__/__init__.cpython-312.pyc,,
|
||||
cffi/__pycache__/_imp_emulation.cpython-312.pyc,,
|
||||
cffi/__pycache__/_shimmed_dist_utils.cpython-312.pyc,,
|
||||
cffi/__pycache__/api.cpython-312.pyc,,
|
||||
cffi/__pycache__/backend_ctypes.cpython-312.pyc,,
|
||||
cffi/__pycache__/cffi_opcode.cpython-312.pyc,,
|
||||
cffi/__pycache__/commontypes.cpython-312.pyc,,
|
||||
cffi/__pycache__/cparser.cpython-312.pyc,,
|
||||
cffi/__pycache__/error.cpython-312.pyc,,
|
||||
cffi/__pycache__/ffiplatform.cpython-312.pyc,,
|
||||
cffi/__pycache__/lock.cpython-312.pyc,,
|
||||
cffi/__pycache__/model.cpython-312.pyc,,
|
||||
cffi/__pycache__/pkgconfig.cpython-312.pyc,,
|
||||
cffi/__pycache__/recompiler.cpython-312.pyc,,
|
||||
cffi/__pycache__/setuptools_ext.cpython-312.pyc,,
|
||||
cffi/__pycache__/vengine_cpy.cpython-312.pyc,,
|
||||
cffi/__pycache__/vengine_gen.cpython-312.pyc,,
|
||||
cffi/__pycache__/verifier.cpython-312.pyc,,
|
||||
cffi/_cffi_errors.h,sha256=zQXt7uR_m8gUW-fI2hJg0KoSkJFwXv8RGUkEDZ177dQ,3908
|
||||
cffi/_cffi_include.h,sha256=Exhmgm9qzHWzWivjfTe0D7Xp4rPUkVxdNuwGhMTMzbw,15055
|
||||
cffi/_embedding.h,sha256=Ai33FHblE7XSpHOCp8kPcWwN5_9BV14OvN0JVa6ITpw,18786
|
||||
cffi/_imp_emulation.py,sha256=RxREG8zAbI2RPGBww90u_5fi8sWdahpdipOoPzkp7C0,2960
|
||||
cffi/_shimmed_dist_utils.py,sha256=Bjj2wm8yZbvFvWEx5AEfmqaqZyZFhYfoyLLQHkXZuao,2230
|
||||
cffi/api.py,sha256=alBv6hZQkjpmZplBphdaRn2lPO9-CORs_M7ixabvZWI,42169
|
||||
cffi/backend_ctypes.py,sha256=h5ZIzLc6BFVXnGyc9xPqZWUS7qGy7yFSDqXe68Sa8z4,42454
|
||||
cffi/cffi_opcode.py,sha256=JDV5l0R0_OadBX_uE7xPPTYtMdmpp8I9UYd6av7aiDU,5731
|
||||
cffi/commontypes.py,sha256=7N6zPtCFlvxXMWhHV08psUjdYIK2XgsN3yo5dgua_v4,2805
|
||||
cffi/cparser.py,sha256=QUTfmlL-aO-MYR8bFGlvAUHc36OQr7XYLe0WLkGFjRo,44790
|
||||
cffi/error.py,sha256=v6xTiS4U0kvDcy4h_BDRo5v39ZQuj-IMRYLv5ETddZs,877
|
||||
cffi/ffiplatform.py,sha256=avxFjdikYGJoEtmJO7ewVmwG_VEVl6EZ_WaNhZYCqv4,3584
|
||||
cffi/lock.py,sha256=l9TTdwMIMpi6jDkJGnQgE9cvTIR7CAntIJr8EGHt3pY,747
|
||||
cffi/model.py,sha256=W30UFQZE73jL5Mx5N81YT77us2W2iJjTm0XYfnwz1cg,21797
|
||||
cffi/parse_c_type.h,sha256=OdwQfwM9ktq6vlCB43exFQmxDBtj2MBNdK8LYl15tjw,5976
|
||||
cffi/pkgconfig.py,sha256=LP1w7vmWvmKwyqLaU1Z243FOWGNQMrgMUZrvgFuOlco,4374
|
||||
cffi/recompiler.py,sha256=78J6lMEEOygXNmjN9-fOFFO3j7eW-iFxSrxfvQb54bY,65509
|
||||
cffi/setuptools_ext.py,sha256=0rCwBJ1W7FHWtiMKfNXsSST88V8UXrui5oeXFlDNLG8,9411
|
||||
cffi/vengine_cpy.py,sha256=oyQKD23kpE0aChUKA8Jg0e723foPiYzLYEdb-J0MiNs,43881
|
||||
cffi/vengine_gen.py,sha256=DUlEIrDiVin1Pnhn1sfoamnS5NLqfJcOdhRoeSNeJRg,26939
|
||||
cffi/verifier.py,sha256=oX8jpaohg2Qm3aHcznidAdvrVm5N4sQYG0a3Eo5mIl4,11182
|
||||
@@ -0,0 +1,6 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: setuptools (80.9.0)
|
||||
Root-Is-Purelib: false
|
||||
Tag: cp312-cp312-manylinux_2_17_x86_64
|
||||
Tag: cp312-cp312-manylinux2014_x86_64
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[distutils.setup_keywords]
|
||||
cffi_modules = cffi.setuptools_ext:cffi_modules
|
||||
@@ -0,0 +1,8 @@
|
||||
This package has been mostly done by Armin Rigo with help from
|
||||
Maciej Fijałkowski. The idea is heavily based (although not directly
|
||||
copied) from LuaJIT ffi by Mike Pall.
|
||||
|
||||
|
||||
Other contributors:
|
||||
|
||||
Google Inc.
|
||||
@@ -0,0 +1,23 @@
|
||||
|
||||
Except when otherwise stated (look for LICENSE files in directories or
|
||||
information at the beginning of each file) all software and
|
||||
documentation is licensed as follows:
|
||||
|
||||
MIT No Attribution
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
_cffi_backend
|
||||
cffi
|
||||
@@ -0,0 +1,14 @@
|
||||
__all__ = ['FFI', 'VerificationError', 'VerificationMissing', 'CDefError',
|
||||
'FFIError']
|
||||
|
||||
from .api import FFI
|
||||
from .error import CDefError, FFIError, VerificationError, VerificationMissing
|
||||
from .error import PkgConfigError
|
||||
|
||||
__version__ = "2.0.0"
|
||||
__version_info__ = (2, 0, 0)
|
||||
|
||||
# The verifier module file names are based on the CRC32 of a string that
|
||||
# contains the following version number. It may be older than __version__
|
||||
# if nothing is clearly incompatible.
|
||||
__version_verifier_modules__ = "0.8.6"
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,149 @@
|
||||
#ifndef CFFI_MESSAGEBOX
|
||||
# ifdef _MSC_VER
|
||||
# define CFFI_MESSAGEBOX 1
|
||||
# else
|
||||
# define CFFI_MESSAGEBOX 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
#if CFFI_MESSAGEBOX
|
||||
/* Windows only: logic to take the Python-CFFI embedding logic
|
||||
initialization errors and display them in a background thread
|
||||
with MessageBox. The idea is that if the whole program closes
|
||||
as a result of this problem, then likely it is already a console
|
||||
program and you can read the stderr output in the console too.
|
||||
If it is not a console program, then it will likely show its own
|
||||
dialog to complain, or generally not abruptly close, and for this
|
||||
case the background thread should stay alive.
|
||||
*/
|
||||
static void *volatile _cffi_bootstrap_text;
|
||||
|
||||
static PyObject *_cffi_start_error_capture(void)
|
||||
{
|
||||
PyObject *result = NULL;
|
||||
PyObject *x, *m, *bi;
|
||||
|
||||
if (InterlockedCompareExchangePointer(&_cffi_bootstrap_text,
|
||||
(void *)1, NULL) != NULL)
|
||||
return (PyObject *)1;
|
||||
|
||||
m = PyImport_AddModule("_cffi_error_capture");
|
||||
if (m == NULL)
|
||||
goto error;
|
||||
|
||||
result = PyModule_GetDict(m);
|
||||
if (result == NULL)
|
||||
goto error;
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
bi = PyImport_ImportModule("builtins");
|
||||
#else
|
||||
bi = PyImport_ImportModule("__builtin__");
|
||||
#endif
|
||||
if (bi == NULL)
|
||||
goto error;
|
||||
PyDict_SetItemString(result, "__builtins__", bi);
|
||||
Py_DECREF(bi);
|
||||
|
||||
x = PyRun_String(
|
||||
"import sys\n"
|
||||
"class FileLike:\n"
|
||||
" def write(self, x):\n"
|
||||
" try:\n"
|
||||
" of.write(x)\n"
|
||||
" except: pass\n"
|
||||
" self.buf += x\n"
|
||||
" def flush(self):\n"
|
||||
" pass\n"
|
||||
"fl = FileLike()\n"
|
||||
"fl.buf = ''\n"
|
||||
"of = sys.stderr\n"
|
||||
"sys.stderr = fl\n"
|
||||
"def done():\n"
|
||||
" sys.stderr = of\n"
|
||||
" return fl.buf\n", /* make sure the returned value stays alive */
|
||||
Py_file_input,
|
||||
result, result);
|
||||
Py_XDECREF(x);
|
||||
|
||||
error:
|
||||
if (PyErr_Occurred())
|
||||
{
|
||||
PyErr_WriteUnraisable(Py_None);
|
||||
PyErr_Clear();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#pragma comment(lib, "user32.lib")
|
||||
|
||||
static DWORD WINAPI _cffi_bootstrap_dialog(LPVOID ignored)
|
||||
{
|
||||
Sleep(666); /* may be interrupted if the whole process is closing */
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
MessageBoxW(NULL, (wchar_t *)_cffi_bootstrap_text,
|
||||
L"Python-CFFI error",
|
||||
MB_OK | MB_ICONERROR);
|
||||
#else
|
||||
MessageBoxA(NULL, (char *)_cffi_bootstrap_text,
|
||||
"Python-CFFI error",
|
||||
MB_OK | MB_ICONERROR);
|
||||
#endif
|
||||
_cffi_bootstrap_text = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _cffi_stop_error_capture(PyObject *ecap)
|
||||
{
|
||||
PyObject *s;
|
||||
void *text;
|
||||
|
||||
if (ecap == (PyObject *)1)
|
||||
return;
|
||||
|
||||
if (ecap == NULL)
|
||||
goto error;
|
||||
|
||||
s = PyRun_String("done()", Py_eval_input, ecap, ecap);
|
||||
if (s == NULL)
|
||||
goto error;
|
||||
|
||||
/* Show a dialog box, but in a background thread, and
|
||||
never show multiple dialog boxes at once. */
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
text = PyUnicode_AsWideCharString(s, NULL);
|
||||
#else
|
||||
text = PyString_AsString(s);
|
||||
#endif
|
||||
|
||||
_cffi_bootstrap_text = text;
|
||||
|
||||
if (text != NULL)
|
||||
{
|
||||
HANDLE h;
|
||||
h = CreateThread(NULL, 0, _cffi_bootstrap_dialog,
|
||||
NULL, 0, NULL);
|
||||
if (h != NULL)
|
||||
CloseHandle(h);
|
||||
}
|
||||
/* decref the string, but it should stay alive as 'fl.buf'
|
||||
in the small module above. It will really be freed only if
|
||||
we later get another similar error. So it's a leak of at
|
||||
most one copy of the small module. That's fine for this
|
||||
situation which is usually a "fatal error" anyway. */
|
||||
Py_DECREF(s);
|
||||
PyErr_Clear();
|
||||
return;
|
||||
|
||||
error:
|
||||
_cffi_bootstrap_text = NULL;
|
||||
PyErr_Clear();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static PyObject *_cffi_start_error_capture(void) { return NULL; }
|
||||
static void _cffi_stop_error_capture(PyObject *ecap) { }
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,389 @@
|
||||
#define _CFFI_
|
||||
|
||||
/* We try to define Py_LIMITED_API before including Python.h.
|
||||
|
||||
Mess: we can only define it if Py_DEBUG, Py_TRACE_REFS and
|
||||
Py_REF_DEBUG are not defined. This is a best-effort approximation:
|
||||
we can learn about Py_DEBUG from pyconfig.h, but it is unclear if
|
||||
the same works for the other two macros. Py_DEBUG implies them,
|
||||
but not the other way around.
|
||||
|
||||
The implementation is messy (issue #350): on Windows, with _MSC_VER,
|
||||
we have to define Py_LIMITED_API even before including pyconfig.h.
|
||||
In that case, we guess what pyconfig.h will do to the macros above,
|
||||
and check our guess after the #include.
|
||||
|
||||
Note that on Windows, with CPython 3.x, you need >= 3.5 and virtualenv
|
||||
version >= 16.0.0. With older versions of either, you don't get a
|
||||
copy of PYTHON3.DLL in the virtualenv. We can't check the version of
|
||||
CPython *before* we even include pyconfig.h. ffi.set_source() puts
|
||||
a ``#define _CFFI_NO_LIMITED_API'' at the start of this file if it is
|
||||
running on Windows < 3.5, as an attempt at fixing it, but that's
|
||||
arguably wrong because it may not be the target version of Python.
|
||||
Still better than nothing I guess. As another workaround, you can
|
||||
remove the definition of Py_LIMITED_API here.
|
||||
|
||||
See also 'py_limited_api' in cffi/setuptools_ext.py.
|
||||
*/
|
||||
#if !defined(_CFFI_USE_EMBEDDING) && !defined(Py_LIMITED_API)
|
||||
# ifdef _MSC_VER
|
||||
# if !defined(_DEBUG) && !defined(Py_DEBUG) && !defined(Py_TRACE_REFS) && !defined(Py_REF_DEBUG) && !defined(_CFFI_NO_LIMITED_API)
|
||||
# define Py_LIMITED_API
|
||||
# endif
|
||||
# include <pyconfig.h>
|
||||
/* sanity-check: Py_LIMITED_API will cause crashes if any of these
|
||||
are also defined. Normally, the Python file PC/pyconfig.h does not
|
||||
cause any of these to be defined, with the exception that _DEBUG
|
||||
causes Py_DEBUG. Double-check that. */
|
||||
# ifdef Py_LIMITED_API
|
||||
# if defined(Py_DEBUG)
|
||||
# error "pyconfig.h unexpectedly defines Py_DEBUG, but Py_LIMITED_API is set"
|
||||
# endif
|
||||
# if defined(Py_TRACE_REFS)
|
||||
# error "pyconfig.h unexpectedly defines Py_TRACE_REFS, but Py_LIMITED_API is set"
|
||||
# endif
|
||||
# if defined(Py_REF_DEBUG)
|
||||
# error "pyconfig.h unexpectedly defines Py_REF_DEBUG, but Py_LIMITED_API is set"
|
||||
# endif
|
||||
# endif
|
||||
# else
|
||||
# include <pyconfig.h>
|
||||
# if !defined(Py_DEBUG) && !defined(Py_TRACE_REFS) && !defined(Py_REF_DEBUG) && !defined(_CFFI_NO_LIMITED_API)
|
||||
# define Py_LIMITED_API
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include <Python.h>
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#include <stddef.h>
|
||||
#include "parse_c_type.h"
|
||||
|
||||
/* this block of #ifs should be kept exactly identical between
|
||||
c/_cffi_backend.c, cffi/vengine_cpy.py, cffi/vengine_gen.py
|
||||
and cffi/_cffi_include.h */
|
||||
#if defined(_MSC_VER)
|
||||
# include <malloc.h> /* for alloca() */
|
||||
# if _MSC_VER < 1600 /* MSVC < 2010 */
|
||||
typedef __int8 int8_t;
|
||||
typedef __int16 int16_t;
|
||||
typedef __int32 int32_t;
|
||||
typedef __int64 int64_t;
|
||||
typedef unsigned __int8 uint8_t;
|
||||
typedef unsigned __int16 uint16_t;
|
||||
typedef unsigned __int32 uint32_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
typedef __int8 int_least8_t;
|
||||
typedef __int16 int_least16_t;
|
||||
typedef __int32 int_least32_t;
|
||||
typedef __int64 int_least64_t;
|
||||
typedef unsigned __int8 uint_least8_t;
|
||||
typedef unsigned __int16 uint_least16_t;
|
||||
typedef unsigned __int32 uint_least32_t;
|
||||
typedef unsigned __int64 uint_least64_t;
|
||||
typedef __int8 int_fast8_t;
|
||||
typedef __int16 int_fast16_t;
|
||||
typedef __int32 int_fast32_t;
|
||||
typedef __int64 int_fast64_t;
|
||||
typedef unsigned __int8 uint_fast8_t;
|
||||
typedef unsigned __int16 uint_fast16_t;
|
||||
typedef unsigned __int32 uint_fast32_t;
|
||||
typedef unsigned __int64 uint_fast64_t;
|
||||
typedef __int64 intmax_t;
|
||||
typedef unsigned __int64 uintmax_t;
|
||||
# else
|
||||
# include <stdint.h>
|
||||
# endif
|
||||
# if _MSC_VER < 1800 /* MSVC < 2013 */
|
||||
# ifndef __cplusplus
|
||||
typedef unsigned char _Bool;
|
||||
# endif
|
||||
# endif
|
||||
# define _cffi_float_complex_t _Fcomplex /* include <complex.h> for it */
|
||||
# define _cffi_double_complex_t _Dcomplex /* include <complex.h> for it */
|
||||
#else
|
||||
# include <stdint.h>
|
||||
# if (defined (__SVR4) && defined (__sun)) || defined(_AIX) || defined(__hpux)
|
||||
# include <alloca.h>
|
||||
# endif
|
||||
# define _cffi_float_complex_t float _Complex
|
||||
# define _cffi_double_complex_t double _Complex
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
# define _CFFI_UNUSED_FN __attribute__((unused))
|
||||
#else
|
||||
# define _CFFI_UNUSED_FN /* nothing */
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
# ifndef _Bool
|
||||
typedef bool _Bool; /* semi-hackish: C++ has no _Bool; bool is builtin */
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/********** CPython-specific section **********/
|
||||
#ifndef PYPY_VERSION
|
||||
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
# define PyInt_FromLong PyLong_FromLong
|
||||
#endif
|
||||
|
||||
#define _cffi_from_c_double PyFloat_FromDouble
|
||||
#define _cffi_from_c_float PyFloat_FromDouble
|
||||
#define _cffi_from_c_long PyInt_FromLong
|
||||
#define _cffi_from_c_ulong PyLong_FromUnsignedLong
|
||||
#define _cffi_from_c_longlong PyLong_FromLongLong
|
||||
#define _cffi_from_c_ulonglong PyLong_FromUnsignedLongLong
|
||||
#define _cffi_from_c__Bool PyBool_FromLong
|
||||
|
||||
#define _cffi_to_c_double PyFloat_AsDouble
|
||||
#define _cffi_to_c_float PyFloat_AsDouble
|
||||
|
||||
#define _cffi_from_c_int(x, type) \
|
||||
(((type)-1) > 0 ? /* unsigned */ \
|
||||
(sizeof(type) < sizeof(long) ? \
|
||||
PyInt_FromLong((long)x) : \
|
||||
sizeof(type) == sizeof(long) ? \
|
||||
PyLong_FromUnsignedLong((unsigned long)x) : \
|
||||
PyLong_FromUnsignedLongLong((unsigned long long)x)) : \
|
||||
(sizeof(type) <= sizeof(long) ? \
|
||||
PyInt_FromLong((long)x) : \
|
||||
PyLong_FromLongLong((long long)x)))
|
||||
|
||||
#define _cffi_to_c_int(o, type) \
|
||||
((type)( \
|
||||
sizeof(type) == 1 ? (((type)-1) > 0 ? (type)_cffi_to_c_u8(o) \
|
||||
: (type)_cffi_to_c_i8(o)) : \
|
||||
sizeof(type) == 2 ? (((type)-1) > 0 ? (type)_cffi_to_c_u16(o) \
|
||||
: (type)_cffi_to_c_i16(o)) : \
|
||||
sizeof(type) == 4 ? (((type)-1) > 0 ? (type)_cffi_to_c_u32(o) \
|
||||
: (type)_cffi_to_c_i32(o)) : \
|
||||
sizeof(type) == 8 ? (((type)-1) > 0 ? (type)_cffi_to_c_u64(o) \
|
||||
: (type)_cffi_to_c_i64(o)) : \
|
||||
(Py_FatalError("unsupported size for type " #type), (type)0)))
|
||||
|
||||
#define _cffi_to_c_i8 \
|
||||
((int(*)(PyObject *))_cffi_exports[1])
|
||||
#define _cffi_to_c_u8 \
|
||||
((int(*)(PyObject *))_cffi_exports[2])
|
||||
#define _cffi_to_c_i16 \
|
||||
((int(*)(PyObject *))_cffi_exports[3])
|
||||
#define _cffi_to_c_u16 \
|
||||
((int(*)(PyObject *))_cffi_exports[4])
|
||||
#define _cffi_to_c_i32 \
|
||||
((int(*)(PyObject *))_cffi_exports[5])
|
||||
#define _cffi_to_c_u32 \
|
||||
((unsigned int(*)(PyObject *))_cffi_exports[6])
|
||||
#define _cffi_to_c_i64 \
|
||||
((long long(*)(PyObject *))_cffi_exports[7])
|
||||
#define _cffi_to_c_u64 \
|
||||
((unsigned long long(*)(PyObject *))_cffi_exports[8])
|
||||
#define _cffi_to_c_char \
|
||||
((int(*)(PyObject *))_cffi_exports[9])
|
||||
#define _cffi_from_c_pointer \
|
||||
((PyObject *(*)(char *, struct _cffi_ctypedescr *))_cffi_exports[10])
|
||||
#define _cffi_to_c_pointer \
|
||||
((char *(*)(PyObject *, struct _cffi_ctypedescr *))_cffi_exports[11])
|
||||
#define _cffi_get_struct_layout \
|
||||
not used any more
|
||||
#define _cffi_restore_errno \
|
||||
((void(*)(void))_cffi_exports[13])
|
||||
#define _cffi_save_errno \
|
||||
((void(*)(void))_cffi_exports[14])
|
||||
#define _cffi_from_c_char \
|
||||
((PyObject *(*)(char))_cffi_exports[15])
|
||||
#define _cffi_from_c_deref \
|
||||
((PyObject *(*)(char *, struct _cffi_ctypedescr *))_cffi_exports[16])
|
||||
#define _cffi_to_c \
|
||||
((int(*)(char *, struct _cffi_ctypedescr *, PyObject *))_cffi_exports[17])
|
||||
#define _cffi_from_c_struct \
|
||||
((PyObject *(*)(char *, struct _cffi_ctypedescr *))_cffi_exports[18])
|
||||
#define _cffi_to_c_wchar_t \
|
||||
((_cffi_wchar_t(*)(PyObject *))_cffi_exports[19])
|
||||
#define _cffi_from_c_wchar_t \
|
||||
((PyObject *(*)(_cffi_wchar_t))_cffi_exports[20])
|
||||
#define _cffi_to_c_long_double \
|
||||
((long double(*)(PyObject *))_cffi_exports[21])
|
||||
#define _cffi_to_c__Bool \
|
||||
((_Bool(*)(PyObject *))_cffi_exports[22])
|
||||
#define _cffi_prepare_pointer_call_argument \
|
||||
((Py_ssize_t(*)(struct _cffi_ctypedescr *, \
|
||||
PyObject *, char **))_cffi_exports[23])
|
||||
#define _cffi_convert_array_from_object \
|
||||
((int(*)(char *, struct _cffi_ctypedescr *, PyObject *))_cffi_exports[24])
|
||||
#define _CFFI_CPIDX 25
|
||||
#define _cffi_call_python \
|
||||
((void(*)(struct _cffi_externpy_s *, char *))_cffi_exports[_CFFI_CPIDX])
|
||||
#define _cffi_to_c_wchar3216_t \
|
||||
((int(*)(PyObject *))_cffi_exports[26])
|
||||
#define _cffi_from_c_wchar3216_t \
|
||||
((PyObject *(*)(int))_cffi_exports[27])
|
||||
#define _CFFI_NUM_EXPORTS 28
|
||||
|
||||
struct _cffi_ctypedescr;
|
||||
|
||||
static void *_cffi_exports[_CFFI_NUM_EXPORTS];
|
||||
|
||||
#define _cffi_type(index) ( \
|
||||
assert((((uintptr_t)_cffi_types[index]) & 1) == 0), \
|
||||
(struct _cffi_ctypedescr *)_cffi_types[index])
|
||||
|
||||
static PyObject *_cffi_init(const char *module_name, Py_ssize_t version,
|
||||
const struct _cffi_type_context_s *ctx)
|
||||
{
|
||||
PyObject *module, *o_arg, *new_module;
|
||||
void *raw[] = {
|
||||
(void *)module_name,
|
||||
(void *)version,
|
||||
(void *)_cffi_exports,
|
||||
(void *)ctx,
|
||||
};
|
||||
|
||||
module = PyImport_ImportModule("_cffi_backend");
|
||||
if (module == NULL)
|
||||
goto failure;
|
||||
|
||||
o_arg = PyLong_FromVoidPtr((void *)raw);
|
||||
if (o_arg == NULL)
|
||||
goto failure;
|
||||
|
||||
new_module = PyObject_CallMethod(
|
||||
module, (char *)"_init_cffi_1_0_external_module", (char *)"O", o_arg);
|
||||
|
||||
Py_DECREF(o_arg);
|
||||
Py_DECREF(module);
|
||||
return new_module;
|
||||
|
||||
failure:
|
||||
Py_XDECREF(module);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
#ifdef HAVE_WCHAR_H
|
||||
typedef wchar_t _cffi_wchar_t;
|
||||
#else
|
||||
typedef uint16_t _cffi_wchar_t; /* same random pick as _cffi_backend.c */
|
||||
#endif
|
||||
|
||||
_CFFI_UNUSED_FN static uint16_t _cffi_to_c_char16_t(PyObject *o)
|
||||
{
|
||||
if (sizeof(_cffi_wchar_t) == 2)
|
||||
return (uint16_t)_cffi_to_c_wchar_t(o);
|
||||
else
|
||||
return (uint16_t)_cffi_to_c_wchar3216_t(o);
|
||||
}
|
||||
|
||||
_CFFI_UNUSED_FN static PyObject *_cffi_from_c_char16_t(uint16_t x)
|
||||
{
|
||||
if (sizeof(_cffi_wchar_t) == 2)
|
||||
return _cffi_from_c_wchar_t((_cffi_wchar_t)x);
|
||||
else
|
||||
return _cffi_from_c_wchar3216_t((int)x);
|
||||
}
|
||||
|
||||
_CFFI_UNUSED_FN static int _cffi_to_c_char32_t(PyObject *o)
|
||||
{
|
||||
if (sizeof(_cffi_wchar_t) == 4)
|
||||
return (int)_cffi_to_c_wchar_t(o);
|
||||
else
|
||||
return (int)_cffi_to_c_wchar3216_t(o);
|
||||
}
|
||||
|
||||
_CFFI_UNUSED_FN static PyObject *_cffi_from_c_char32_t(unsigned int x)
|
||||
{
|
||||
if (sizeof(_cffi_wchar_t) == 4)
|
||||
return _cffi_from_c_wchar_t((_cffi_wchar_t)x);
|
||||
else
|
||||
return _cffi_from_c_wchar3216_t((int)x);
|
||||
}
|
||||
|
||||
union _cffi_union_alignment_u {
|
||||
unsigned char m_char;
|
||||
unsigned short m_short;
|
||||
unsigned int m_int;
|
||||
unsigned long m_long;
|
||||
unsigned long long m_longlong;
|
||||
float m_float;
|
||||
double m_double;
|
||||
long double m_longdouble;
|
||||
};
|
||||
|
||||
struct _cffi_freeme_s {
|
||||
struct _cffi_freeme_s *next;
|
||||
union _cffi_union_alignment_u alignment;
|
||||
};
|
||||
|
||||
_CFFI_UNUSED_FN static int
|
||||
_cffi_convert_array_argument(struct _cffi_ctypedescr *ctptr, PyObject *arg,
|
||||
char **output_data, Py_ssize_t datasize,
|
||||
struct _cffi_freeme_s **freeme)
|
||||
{
|
||||
char *p;
|
||||
if (datasize < 0)
|
||||
return -1;
|
||||
|
||||
p = *output_data;
|
||||
if (p == NULL) {
|
||||
struct _cffi_freeme_s *fp = (struct _cffi_freeme_s *)PyObject_Malloc(
|
||||
offsetof(struct _cffi_freeme_s, alignment) + (size_t)datasize);
|
||||
if (fp == NULL)
|
||||
return -1;
|
||||
fp->next = *freeme;
|
||||
*freeme = fp;
|
||||
p = *output_data = (char *)&fp->alignment;
|
||||
}
|
||||
memset((void *)p, 0, (size_t)datasize);
|
||||
return _cffi_convert_array_from_object(p, ctptr, arg);
|
||||
}
|
||||
|
||||
_CFFI_UNUSED_FN static void
|
||||
_cffi_free_array_arguments(struct _cffi_freeme_s *freeme)
|
||||
{
|
||||
do {
|
||||
void *p = (void *)freeme;
|
||||
freeme = freeme->next;
|
||||
PyObject_Free(p);
|
||||
} while (freeme != NULL);
|
||||
}
|
||||
|
||||
/********** end CPython-specific section **********/
|
||||
#else
|
||||
_CFFI_UNUSED_FN
|
||||
static void (*_cffi_call_python_org)(struct _cffi_externpy_s *, char *);
|
||||
# define _cffi_call_python _cffi_call_python_org
|
||||
#endif
|
||||
|
||||
|
||||
#define _cffi_array_len(array) (sizeof(array) / sizeof((array)[0]))
|
||||
|
||||
#define _cffi_prim_int(size, sign) \
|
||||
((size) == 1 ? ((sign) ? _CFFI_PRIM_INT8 : _CFFI_PRIM_UINT8) : \
|
||||
(size) == 2 ? ((sign) ? _CFFI_PRIM_INT16 : _CFFI_PRIM_UINT16) : \
|
||||
(size) == 4 ? ((sign) ? _CFFI_PRIM_INT32 : _CFFI_PRIM_UINT32) : \
|
||||
(size) == 8 ? ((sign) ? _CFFI_PRIM_INT64 : _CFFI_PRIM_UINT64) : \
|
||||
_CFFI__UNKNOWN_PRIM)
|
||||
|
||||
#define _cffi_prim_float(size) \
|
||||
((size) == sizeof(float) ? _CFFI_PRIM_FLOAT : \
|
||||
(size) == sizeof(double) ? _CFFI_PRIM_DOUBLE : \
|
||||
(size) == sizeof(long double) ? _CFFI__UNKNOWN_LONG_DOUBLE : \
|
||||
_CFFI__UNKNOWN_FLOAT_PRIM)
|
||||
|
||||
#define _cffi_check_int(got, got_nonpos, expected) \
|
||||
((got_nonpos) == (expected <= 0) && \
|
||||
(got) == (unsigned long long)expected)
|
||||
|
||||
#ifdef MS_WIN32
|
||||
# define _cffi_stdcall __stdcall
|
||||
#else
|
||||
# define _cffi_stdcall /* nothing */
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,550 @@
|
||||
|
||||
/***** Support code for embedding *****/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(_WIN32)
|
||||
# define CFFI_DLLEXPORT __declspec(dllexport)
|
||||
#elif defined(__GNUC__)
|
||||
# define CFFI_DLLEXPORT __attribute__((visibility("default")))
|
||||
#else
|
||||
# define CFFI_DLLEXPORT /* nothing */
|
||||
#endif
|
||||
|
||||
|
||||
/* There are two global variables of type _cffi_call_python_fnptr:
|
||||
|
||||
* _cffi_call_python, which we declare just below, is the one called
|
||||
by ``extern "Python"`` implementations.
|
||||
|
||||
* _cffi_call_python_org, which on CPython is actually part of the
|
||||
_cffi_exports[] array, is the function pointer copied from
|
||||
_cffi_backend. If _cffi_start_python() fails, then this is set
|
||||
to NULL; otherwise, it should never be NULL.
|
||||
|
||||
After initialization is complete, both are equal. However, the
|
||||
first one remains equal to &_cffi_start_and_call_python until the
|
||||
very end of initialization, when we are (or should be) sure that
|
||||
concurrent threads also see a completely initialized world, and
|
||||
only then is it changed.
|
||||
*/
|
||||
#undef _cffi_call_python
|
||||
typedef void (*_cffi_call_python_fnptr)(struct _cffi_externpy_s *, char *);
|
||||
static void _cffi_start_and_call_python(struct _cffi_externpy_s *, char *);
|
||||
static _cffi_call_python_fnptr _cffi_call_python = &_cffi_start_and_call_python;
|
||||
|
||||
|
||||
#ifndef _MSC_VER
|
||||
/* --- Assuming a GCC not infinitely old --- */
|
||||
# define cffi_compare_and_swap(l,o,n) __sync_bool_compare_and_swap(l,o,n)
|
||||
# define cffi_write_barrier() __sync_synchronize()
|
||||
# if !defined(__amd64__) && !defined(__x86_64__) && \
|
||||
!defined(__i386__) && !defined(__i386)
|
||||
# define cffi_read_barrier() __sync_synchronize()
|
||||
# else
|
||||
# define cffi_read_barrier() (void)0
|
||||
# endif
|
||||
#else
|
||||
/* --- Windows threads version --- */
|
||||
# include <Windows.h>
|
||||
# define cffi_compare_and_swap(l,o,n) \
|
||||
(InterlockedCompareExchangePointer(l,n,o) == (o))
|
||||
# define cffi_write_barrier() InterlockedCompareExchange(&_cffi_dummy,0,0)
|
||||
# define cffi_read_barrier() (void)0
|
||||
static volatile LONG _cffi_dummy;
|
||||
#endif
|
||||
|
||||
#ifdef WITH_THREAD
|
||||
# ifndef _MSC_VER
|
||||
# include <pthread.h>
|
||||
static pthread_mutex_t _cffi_embed_startup_lock;
|
||||
# else
|
||||
static CRITICAL_SECTION _cffi_embed_startup_lock;
|
||||
# endif
|
||||
static char _cffi_embed_startup_lock_ready = 0;
|
||||
#endif
|
||||
|
||||
static void _cffi_acquire_reentrant_mutex(void)
|
||||
{
|
||||
static void *volatile lock = NULL;
|
||||
|
||||
while (!cffi_compare_and_swap(&lock, NULL, (void *)1)) {
|
||||
/* should ideally do a spin loop instruction here, but
|
||||
hard to do it portably and doesn't really matter I
|
||||
think: pthread_mutex_init() should be very fast, and
|
||||
this is only run at start-up anyway. */
|
||||
}
|
||||
|
||||
#ifdef WITH_THREAD
|
||||
if (!_cffi_embed_startup_lock_ready) {
|
||||
# ifndef _MSC_VER
|
||||
pthread_mutexattr_t attr;
|
||||
pthread_mutexattr_init(&attr);
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
pthread_mutex_init(&_cffi_embed_startup_lock, &attr);
|
||||
# else
|
||||
InitializeCriticalSection(&_cffi_embed_startup_lock);
|
||||
# endif
|
||||
_cffi_embed_startup_lock_ready = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
while (!cffi_compare_and_swap(&lock, (void *)1, NULL))
|
||||
;
|
||||
|
||||
#ifndef _MSC_VER
|
||||
pthread_mutex_lock(&_cffi_embed_startup_lock);
|
||||
#else
|
||||
EnterCriticalSection(&_cffi_embed_startup_lock);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void _cffi_release_reentrant_mutex(void)
|
||||
{
|
||||
#ifndef _MSC_VER
|
||||
pthread_mutex_unlock(&_cffi_embed_startup_lock);
|
||||
#else
|
||||
LeaveCriticalSection(&_cffi_embed_startup_lock);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/********** CPython-specific section **********/
|
||||
#ifndef PYPY_VERSION
|
||||
|
||||
#include "_cffi_errors.h"
|
||||
|
||||
|
||||
#define _cffi_call_python_org _cffi_exports[_CFFI_CPIDX]
|
||||
|
||||
PyMODINIT_FUNC _CFFI_PYTHON_STARTUP_FUNC(void); /* forward */
|
||||
|
||||
static void _cffi_py_initialize(void)
|
||||
{
|
||||
/* XXX use initsigs=0, which "skips initialization registration of
|
||||
signal handlers, which might be useful when Python is
|
||||
embedded" according to the Python docs. But review and think
|
||||
if it should be a user-controllable setting.
|
||||
|
||||
XXX we should also give a way to write errors to a buffer
|
||||
instead of to stderr.
|
||||
|
||||
XXX if importing 'site' fails, CPython (any version) calls
|
||||
exit(). Should we try to work around this behavior here?
|
||||
*/
|
||||
Py_InitializeEx(0);
|
||||
}
|
||||
|
||||
static int _cffi_initialize_python(void)
|
||||
{
|
||||
/* This initializes Python, imports _cffi_backend, and then the
|
||||
present .dll/.so is set up as a CPython C extension module.
|
||||
*/
|
||||
int result;
|
||||
PyGILState_STATE state;
|
||||
PyObject *pycode=NULL, *global_dict=NULL, *x;
|
||||
PyObject *builtins;
|
||||
|
||||
state = PyGILState_Ensure();
|
||||
|
||||
/* Call the initxxx() function from the present module. It will
|
||||
create and initialize us as a CPython extension module, instead
|
||||
of letting the startup Python code do it---it might reimport
|
||||
the same .dll/.so and get maybe confused on some platforms.
|
||||
It might also have troubles locating the .dll/.so again for all
|
||||
I know.
|
||||
*/
|
||||
(void)_CFFI_PYTHON_STARTUP_FUNC();
|
||||
if (PyErr_Occurred())
|
||||
goto error;
|
||||
|
||||
/* Now run the Python code provided to ffi.embedding_init_code().
|
||||
*/
|
||||
pycode = Py_CompileString(_CFFI_PYTHON_STARTUP_CODE,
|
||||
"<init code for '" _CFFI_MODULE_NAME "'>",
|
||||
Py_file_input);
|
||||
if (pycode == NULL)
|
||||
goto error;
|
||||
global_dict = PyDict_New();
|
||||
if (global_dict == NULL)
|
||||
goto error;
|
||||
builtins = PyEval_GetBuiltins();
|
||||
if (builtins == NULL)
|
||||
goto error;
|
||||
if (PyDict_SetItemString(global_dict, "__builtins__", builtins) < 0)
|
||||
goto error;
|
||||
x = PyEval_EvalCode(
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
(PyCodeObject *)
|
||||
#endif
|
||||
pycode, global_dict, global_dict);
|
||||
if (x == NULL)
|
||||
goto error;
|
||||
Py_DECREF(x);
|
||||
|
||||
/* Done! Now if we've been called from
|
||||
_cffi_start_and_call_python() in an ``extern "Python"``, we can
|
||||
only hope that the Python code did correctly set up the
|
||||
corresponding @ffi.def_extern() function. Otherwise, the
|
||||
general logic of ``extern "Python"`` functions (inside the
|
||||
_cffi_backend module) will find that the reference is still
|
||||
missing and print an error.
|
||||
*/
|
||||
result = 0;
|
||||
done:
|
||||
Py_XDECREF(pycode);
|
||||
Py_XDECREF(global_dict);
|
||||
PyGILState_Release(state);
|
||||
return result;
|
||||
|
||||
error:;
|
||||
{
|
||||
/* Print as much information as potentially useful.
|
||||
Debugging load-time failures with embedding is not fun
|
||||
*/
|
||||
PyObject *ecap;
|
||||
PyObject *exception, *v, *tb, *f, *modules, *mod;
|
||||
PyErr_Fetch(&exception, &v, &tb);
|
||||
ecap = _cffi_start_error_capture();
|
||||
f = PySys_GetObject((char *)"stderr");
|
||||
if (f != NULL && f != Py_None) {
|
||||
PyFile_WriteString(
|
||||
"Failed to initialize the Python-CFFI embedding logic:\n\n", f);
|
||||
}
|
||||
|
||||
if (exception != NULL) {
|
||||
PyErr_NormalizeException(&exception, &v, &tb);
|
||||
PyErr_Display(exception, v, tb);
|
||||
}
|
||||
Py_XDECREF(exception);
|
||||
Py_XDECREF(v);
|
||||
Py_XDECREF(tb);
|
||||
|
||||
if (f != NULL && f != Py_None) {
|
||||
PyFile_WriteString("\nFrom: " _CFFI_MODULE_NAME
|
||||
"\ncompiled with cffi version: 2.0.0"
|
||||
"\n_cffi_backend module: ", f);
|
||||
modules = PyImport_GetModuleDict();
|
||||
mod = PyDict_GetItemString(modules, "_cffi_backend");
|
||||
if (mod == NULL) {
|
||||
PyFile_WriteString("not loaded", f);
|
||||
}
|
||||
else {
|
||||
v = PyObject_GetAttrString(mod, "__file__");
|
||||
PyFile_WriteObject(v, f, 0);
|
||||
Py_XDECREF(v);
|
||||
}
|
||||
PyFile_WriteString("\nsys.path: ", f);
|
||||
PyFile_WriteObject(PySys_GetObject((char *)"path"), f, 0);
|
||||
PyFile_WriteString("\n\n", f);
|
||||
}
|
||||
_cffi_stop_error_capture(ecap);
|
||||
}
|
||||
result = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
#if PY_VERSION_HEX < 0x03080000
|
||||
PyAPI_DATA(char *) _PyParser_TokenNames[]; /* from CPython */
|
||||
#endif
|
||||
|
||||
static int _cffi_carefully_make_gil(void)
|
||||
{
|
||||
/* This does the basic initialization of Python. It can be called
|
||||
completely concurrently from unrelated threads. It assumes
|
||||
that we don't hold the GIL before (if it exists), and we don't
|
||||
hold it afterwards.
|
||||
|
||||
(What it really does used to be completely different in Python 2
|
||||
and Python 3, with the Python 2 solution avoiding the spin-lock
|
||||
around the Py_InitializeEx() call. However, after recent changes
|
||||
to CPython 2.7 (issue #358) it no longer works. So we use the
|
||||
Python 3 solution everywhere.)
|
||||
|
||||
This initializes Python by calling Py_InitializeEx().
|
||||
Important: this must not be called concurrently at all.
|
||||
So we use a global variable as a simple spin lock. This global
|
||||
variable must be from 'libpythonX.Y.so', not from this
|
||||
cffi-based extension module, because it must be shared from
|
||||
different cffi-based extension modules.
|
||||
|
||||
In Python < 3.8, we choose
|
||||
_PyParser_TokenNames[0] as a completely arbitrary pointer value
|
||||
that is never written to. The default is to point to the
|
||||
string "ENDMARKER". We change it temporarily to point to the
|
||||
next character in that string. (Yes, I know it's REALLY
|
||||
obscure.)
|
||||
|
||||
In Python >= 3.8, this string array is no longer writable, so
|
||||
instead we pick PyCapsuleType.tp_version_tag. We can't change
|
||||
Python < 3.8 because someone might use a mixture of cffi
|
||||
embedded modules, some of which were compiled before this file
|
||||
changed.
|
||||
|
||||
In Python >= 3.12, this stopped working because that particular
|
||||
tp_version_tag gets modified during interpreter startup. It's
|
||||
arguably a bad idea before 3.12 too, but again we can't change
|
||||
that because someone might use a mixture of cffi embedded
|
||||
modules, and no-one reported a bug so far. In Python >= 3.12
|
||||
we go instead for PyCapsuleType.tp_as_buffer, which is supposed
|
||||
to always be NULL. We write to it temporarily a pointer to
|
||||
a struct full of NULLs, which is semantically the same.
|
||||
*/
|
||||
|
||||
#ifdef WITH_THREAD
|
||||
# if PY_VERSION_HEX < 0x03080000
|
||||
char *volatile *lock = (char *volatile *)_PyParser_TokenNames;
|
||||
char *old_value, *locked_value;
|
||||
|
||||
while (1) { /* spin loop */
|
||||
old_value = *lock;
|
||||
locked_value = old_value + 1;
|
||||
if (old_value[0] == 'E') {
|
||||
assert(old_value[1] == 'N');
|
||||
if (cffi_compare_and_swap(lock, old_value, locked_value))
|
||||
break;
|
||||
}
|
||||
else {
|
||||
assert(old_value[0] == 'N');
|
||||
/* should ideally do a spin loop instruction here, but
|
||||
hard to do it portably and doesn't really matter I
|
||||
think: PyEval_InitThreads() should be very fast, and
|
||||
this is only run at start-up anyway. */
|
||||
}
|
||||
}
|
||||
# else
|
||||
# if PY_VERSION_HEX < 0x030C0000
|
||||
int volatile *lock = (int volatile *)&PyCapsule_Type.tp_version_tag;
|
||||
int old_value, locked_value = -42;
|
||||
assert(!(PyCapsule_Type.tp_flags & Py_TPFLAGS_HAVE_VERSION_TAG));
|
||||
# else
|
||||
static struct ebp_s { PyBufferProcs buf; int mark; } empty_buffer_procs;
|
||||
empty_buffer_procs.mark = -42;
|
||||
PyBufferProcs *volatile *lock = (PyBufferProcs *volatile *)
|
||||
&PyCapsule_Type.tp_as_buffer;
|
||||
PyBufferProcs *old_value, *locked_value = &empty_buffer_procs.buf;
|
||||
# endif
|
||||
|
||||
while (1) { /* spin loop */
|
||||
old_value = *lock;
|
||||
if (old_value == 0) {
|
||||
if (cffi_compare_and_swap(lock, old_value, locked_value))
|
||||
break;
|
||||
}
|
||||
else {
|
||||
# if PY_VERSION_HEX < 0x030C0000
|
||||
assert(old_value == locked_value);
|
||||
# else
|
||||
/* The pointer should point to a possibly different
|
||||
empty_buffer_procs from another C extension module */
|
||||
assert(((struct ebp_s *)old_value)->mark == -42);
|
||||
# endif
|
||||
/* should ideally do a spin loop instruction here, but
|
||||
hard to do it portably and doesn't really matter I
|
||||
think: PyEval_InitThreads() should be very fast, and
|
||||
this is only run at start-up anyway. */
|
||||
}
|
||||
}
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* call Py_InitializeEx() */
|
||||
if (!Py_IsInitialized()) {
|
||||
_cffi_py_initialize();
|
||||
#if PY_VERSION_HEX < 0x03070000
|
||||
PyEval_InitThreads();
|
||||
#endif
|
||||
PyEval_SaveThread(); /* release the GIL */
|
||||
/* the returned tstate must be the one that has been stored into the
|
||||
autoTLSkey by _PyGILState_Init() called from Py_Initialize(). */
|
||||
}
|
||||
else {
|
||||
#if PY_VERSION_HEX < 0x03070000
|
||||
/* PyEval_InitThreads() is always a no-op from CPython 3.7 */
|
||||
PyGILState_STATE state = PyGILState_Ensure();
|
||||
PyEval_InitThreads();
|
||||
PyGILState_Release(state);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef WITH_THREAD
|
||||
/* release the lock */
|
||||
while (!cffi_compare_and_swap(lock, locked_value, old_value))
|
||||
;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/********** end CPython-specific section **********/
|
||||
|
||||
|
||||
#else
|
||||
|
||||
|
||||
/********** PyPy-specific section **********/
|
||||
|
||||
PyMODINIT_FUNC _CFFI_PYTHON_STARTUP_FUNC(const void *[]); /* forward */
|
||||
|
||||
static struct _cffi_pypy_init_s {
|
||||
const char *name;
|
||||
void *func; /* function pointer */
|
||||
const char *code;
|
||||
} _cffi_pypy_init = {
|
||||
_CFFI_MODULE_NAME,
|
||||
_CFFI_PYTHON_STARTUP_FUNC,
|
||||
_CFFI_PYTHON_STARTUP_CODE,
|
||||
};
|
||||
|
||||
extern int pypy_carefully_make_gil(const char *);
|
||||
extern int pypy_init_embedded_cffi_module(int, struct _cffi_pypy_init_s *);
|
||||
|
||||
static int _cffi_carefully_make_gil(void)
|
||||
{
|
||||
return pypy_carefully_make_gil(_CFFI_MODULE_NAME);
|
||||
}
|
||||
|
||||
static int _cffi_initialize_python(void)
|
||||
{
|
||||
return pypy_init_embedded_cffi_module(0xB011, &_cffi_pypy_init);
|
||||
}
|
||||
|
||||
/********** end PyPy-specific section **********/
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef __GNUC__
|
||||
__attribute__((noinline))
|
||||
#endif
|
||||
static _cffi_call_python_fnptr _cffi_start_python(void)
|
||||
{
|
||||
/* Delicate logic to initialize Python. This function can be
|
||||
called multiple times concurrently, e.g. when the process calls
|
||||
its first ``extern "Python"`` functions in multiple threads at
|
||||
once. It can also be called recursively, in which case we must
|
||||
ignore it. We also have to consider what occurs if several
|
||||
different cffi-based extensions reach this code in parallel
|
||||
threads---it is a different copy of the code, then, and we
|
||||
can't have any shared global variable unless it comes from
|
||||
'libpythonX.Y.so'.
|
||||
|
||||
Idea:
|
||||
|
||||
* _cffi_carefully_make_gil(): "carefully" call
|
||||
PyEval_InitThreads() (possibly with Py_InitializeEx() first).
|
||||
|
||||
* then we use a (local) custom lock to make sure that a call to this
|
||||
cffi-based extension will wait if another call to the *same*
|
||||
extension is running the initialization in another thread.
|
||||
It is reentrant, so that a recursive call will not block, but
|
||||
only one from a different thread.
|
||||
|
||||
* then we grab the GIL and (Python 2) we call Py_InitializeEx().
|
||||
At this point, concurrent calls to Py_InitializeEx() are not
|
||||
possible: we have the GIL.
|
||||
|
||||
* do the rest of the specific initialization, which may
|
||||
temporarily release the GIL but not the custom lock.
|
||||
Only release the custom lock when we are done.
|
||||
*/
|
||||
static char called = 0;
|
||||
|
||||
if (_cffi_carefully_make_gil() != 0)
|
||||
return NULL;
|
||||
|
||||
_cffi_acquire_reentrant_mutex();
|
||||
|
||||
/* Here the GIL exists, but we don't have it. We're only protected
|
||||
from concurrency by the reentrant mutex. */
|
||||
|
||||
/* This file only initializes the embedded module once, the first
|
||||
time this is called, even if there are subinterpreters. */
|
||||
if (!called) {
|
||||
called = 1; /* invoke _cffi_initialize_python() only once,
|
||||
but don't set '_cffi_call_python' right now,
|
||||
otherwise concurrent threads won't call
|
||||
this function at all (we need them to wait) */
|
||||
if (_cffi_initialize_python() == 0) {
|
||||
/* now initialization is finished. Switch to the fast-path. */
|
||||
|
||||
/* We would like nobody to see the new value of
|
||||
'_cffi_call_python' without also seeing the rest of the
|
||||
data initialized. However, this is not possible. But
|
||||
the new value of '_cffi_call_python' is the function
|
||||
'cffi_call_python()' from _cffi_backend. So: */
|
||||
cffi_write_barrier();
|
||||
/* ^^^ we put a write barrier here, and a corresponding
|
||||
read barrier at the start of cffi_call_python(). This
|
||||
ensures that after that read barrier, we see everything
|
||||
done here before the write barrier.
|
||||
*/
|
||||
|
||||
assert(_cffi_call_python_org != NULL);
|
||||
_cffi_call_python = (_cffi_call_python_fnptr)_cffi_call_python_org;
|
||||
}
|
||||
else {
|
||||
/* initialization failed. Reset this to NULL, even if it was
|
||||
already set to some other value. Future calls to
|
||||
_cffi_start_python() are still forced to occur, and will
|
||||
always return NULL from now on. */
|
||||
_cffi_call_python_org = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
_cffi_release_reentrant_mutex();
|
||||
|
||||
return (_cffi_call_python_fnptr)_cffi_call_python_org;
|
||||
}
|
||||
|
||||
static
|
||||
void _cffi_start_and_call_python(struct _cffi_externpy_s *externpy, char *args)
|
||||
{
|
||||
_cffi_call_python_fnptr fnptr;
|
||||
int current_err = errno;
|
||||
#ifdef _MSC_VER
|
||||
int current_lasterr = GetLastError();
|
||||
#endif
|
||||
fnptr = _cffi_start_python();
|
||||
if (fnptr == NULL) {
|
||||
fprintf(stderr, "function %s() called, but initialization code "
|
||||
"failed. Returning 0.\n", externpy->name);
|
||||
memset(args, 0, externpy->size_of_result);
|
||||
}
|
||||
#ifdef _MSC_VER
|
||||
SetLastError(current_lasterr);
|
||||
#endif
|
||||
errno = current_err;
|
||||
|
||||
if (fnptr != NULL)
|
||||
fnptr(externpy, args);
|
||||
}
|
||||
|
||||
|
||||
/* The cffi_start_python() function makes sure Python is initialized
|
||||
and our cffi module is set up. It can be called manually from the
|
||||
user C code. The same effect is obtained automatically from any
|
||||
dll-exported ``extern "Python"`` function. This function returns
|
||||
-1 if initialization failed, 0 if all is OK. */
|
||||
_CFFI_UNUSED_FN
|
||||
static int cffi_start_python(void)
|
||||
{
|
||||
if (_cffi_call_python == &_cffi_start_and_call_python) {
|
||||
if (_cffi_start_python() == NULL)
|
||||
return -1;
|
||||
}
|
||||
cffi_read_barrier();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef cffi_compare_and_swap
|
||||
#undef cffi_write_barrier
|
||||
#undef cffi_read_barrier
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,83 @@
|
||||
|
||||
try:
|
||||
# this works on Python < 3.12
|
||||
from imp import *
|
||||
|
||||
except ImportError:
|
||||
# this is a limited emulation for Python >= 3.12.
|
||||
# Note that this is used only for tests or for the old ffi.verify().
|
||||
# This is copied from the source code of Python 3.11.
|
||||
|
||||
from _imp import (acquire_lock, release_lock,
|
||||
is_builtin, is_frozen)
|
||||
|
||||
from importlib._bootstrap import _load
|
||||
|
||||
from importlib import machinery
|
||||
import os
|
||||
import sys
|
||||
import tokenize
|
||||
|
||||
SEARCH_ERROR = 0
|
||||
PY_SOURCE = 1
|
||||
PY_COMPILED = 2
|
||||
C_EXTENSION = 3
|
||||
PY_RESOURCE = 4
|
||||
PKG_DIRECTORY = 5
|
||||
C_BUILTIN = 6
|
||||
PY_FROZEN = 7
|
||||
PY_CODERESOURCE = 8
|
||||
IMP_HOOK = 9
|
||||
|
||||
def get_suffixes():
|
||||
extensions = [(s, 'rb', C_EXTENSION)
|
||||
for s in machinery.EXTENSION_SUFFIXES]
|
||||
source = [(s, 'r', PY_SOURCE) for s in machinery.SOURCE_SUFFIXES]
|
||||
bytecode = [(s, 'rb', PY_COMPILED) for s in machinery.BYTECODE_SUFFIXES]
|
||||
return extensions + source + bytecode
|
||||
|
||||
def find_module(name, path=None):
|
||||
if not isinstance(name, str):
|
||||
raise TypeError("'name' must be a str, not {}".format(type(name)))
|
||||
elif not isinstance(path, (type(None), list)):
|
||||
# Backwards-compatibility
|
||||
raise RuntimeError("'path' must be None or a list, "
|
||||
"not {}".format(type(path)))
|
||||
|
||||
if path is None:
|
||||
if is_builtin(name):
|
||||
return None, None, ('', '', C_BUILTIN)
|
||||
elif is_frozen(name):
|
||||
return None, None, ('', '', PY_FROZEN)
|
||||
else:
|
||||
path = sys.path
|
||||
|
||||
for entry in path:
|
||||
package_directory = os.path.join(entry, name)
|
||||
for suffix in ['.py', machinery.BYTECODE_SUFFIXES[0]]:
|
||||
package_file_name = '__init__' + suffix
|
||||
file_path = os.path.join(package_directory, package_file_name)
|
||||
if os.path.isfile(file_path):
|
||||
return None, package_directory, ('', '', PKG_DIRECTORY)
|
||||
for suffix, mode, type_ in get_suffixes():
|
||||
file_name = name + suffix
|
||||
file_path = os.path.join(entry, file_name)
|
||||
if os.path.isfile(file_path):
|
||||
break
|
||||
else:
|
||||
continue
|
||||
break # Break out of outer loop when breaking out of inner loop.
|
||||
else:
|
||||
raise ImportError(name, name=name)
|
||||
|
||||
encoding = None
|
||||
if 'b' not in mode:
|
||||
with open(file_path, 'rb') as file:
|
||||
encoding = tokenize.detect_encoding(file.readline)[0]
|
||||
file = open(file_path, mode, encoding=encoding)
|
||||
return file, file_path, (suffix, mode, type_)
|
||||
|
||||
def load_dynamic(name, path, file=None):
|
||||
loader = machinery.ExtensionFileLoader(name, path)
|
||||
spec = machinery.ModuleSpec(name=name, loader=loader, origin=path)
|
||||
return _load(spec)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user