From cc283b9b99c7ff137cc07a2dfd1cf33e790d5df2 Mon Sep 17 00:00:00 2001 From: lasseedfast Date: Wed, 14 May 2025 17:25:12 -0400 Subject: [PATCH] feat: Initialize Hindenburg Python API with transcription and project management capabilities - Added .gitignore to exclude unnecessary files and directories. - Created Interview1.json, Interview1.srt, Interview1.tsv, Interview1.txt, and Interview1.vtt for transcription data. - Implemented requirements.txt for dependencies including xmltodict, json, lxml, and pytest. - Developed setup.py for package configuration and installation. - Established the hindenburg_api package structure with core functionalities for audio and project handling. - Implemented transcription functionality using WhisperX in transcription.py. - Added example scripts for adding transcriptions and modifying clip colors. - Created a Streamlit application for user-friendly transcription management. - Developed unit tests for project and transcription functionalities. - Included demo project file (demo.nhsx) for testing purposes. --- .gitignore | 62 +++++++++ Interview1.json | 1 + Interview1.srt | 12 ++ Interview1.tsv | 4 + Interview1.txt | 3 + Interview1.vtt | 11 ++ requirements.txt | 4 + setup.py | 21 +++ src/__init__.py | 0 src/examples/add_transcription.py | 33 +++++ src/examples/modify_clip_color.py | 30 ++++ src/hindenburg_api/__init__.py | 1 + src/hindenburg_api/audio.py | 31 +++++ src/hindenburg_api/project.py | 120 ++++++++++++++++ src/hindenburg_api/test.py | 45 ++++++ src/hindenburg_api/transcription.py | 80 +++++++++++ src/hindenburg_api/utils.py | 32 +++++ streamlit_transcribe.py | 205 ++++++++++++++++++++++++++++ tests/__init__.py | 1 + tests/demo_project/demo.nhsx | 79 +++++++++++ tests/test_project.py | 30 ++++ tests/test_transcription.py | 43 ++++++ whisperX | 1 + 23 files changed, 849 insertions(+) create mode 100644 .gitignore create mode 100644 Interview1.json create mode 100644 Interview1.srt create mode 100644 Interview1.tsv create mode 100644 Interview1.txt create mode 100644 Interview1.vtt create mode 100644 requirements.txt create mode 100644 setup.py create mode 100644 src/__init__.py create mode 100644 src/examples/add_transcription.py create mode 100644 src/examples/modify_clip_color.py create mode 100644 src/hindenburg_api/__init__.py create mode 100644 src/hindenburg_api/audio.py create mode 100644 src/hindenburg_api/project.py create mode 100644 src/hindenburg_api/test.py create mode 100644 src/hindenburg_api/transcription.py create mode 100644 src/hindenburg_api/utils.py create mode 100644 streamlit_transcribe.py create mode 100644 tests/__init__.py create mode 100644 tests/demo_project/demo.nhsx create mode 100644 tests/test_project.py create mode 100644 tests/test_transcription.py create mode 160000 whisperX diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6bc901b --- /dev/null +++ b/.gitignore @@ -0,0 +1,62 @@ +# Python virtual environments +.venv/ +venv/ +env/ +ENV/ + +# Python bytecode +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python + +# Distribution / packaging +dist/ +build/ +*.egg-info/ + +# Jupyter Notebook +.ipynb_checkpoints + +# WhisperX specific +whisperx_downloads/ +*.bin +*.pt +*.pth +*.onnx +*.wav +*.mp3 + +# Temporary files generated by streamlit +temp_*.nhsx + +# Test audio files +tests/demo_project/demo Files/*.wav +tests/demo_project/demo Files/*.mp3 + +# Logs +*.log +logs/ + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Environment variables +.env +.env.local + +# IDE specific files +.idea/ +.vscode/ +*.swp +*.swo +.DS_Store \ No newline at end of file diff --git a/Interview1.json b/Interview1.json new file mode 100644 index 0000000..cb07e4c --- /dev/null +++ b/Interview1.json @@ -0,0 +1 @@ +{"segments": [{"start": 0.031, "end": 6.686, "text": " So first, if you just want to tell me your name and a short introduction, like, what are you doing?", "words": [{"word": "So", "start": 0.031, "end": 0.493, "score": 0.743}, {"word": "first,", "start": 0.574, "end": 0.916, "score": 0.807}, {"word": "if", "start": 0.936, "end": 1.016, "score": 0.57}, {"word": "you", "start": 1.077, "end": 1.278, "score": 0.935}, {"word": "just", "start": 1.358, "end": 1.539, "score": 0.764}, {"word": "want", "start": 1.579, "end": 1.8, "score": 0.886}, {"word": "to", "start": 1.841, "end": 1.961, "score": 0.774}, {"word": "tell", "start": 2.102, "end": 2.303, "score": 0.988}, {"word": "me", "start": 2.343, "end": 2.444, "score": 0.655}, {"word": "your", "start": 2.484, "end": 2.605, "score": 0.91}, {"word": "name", "start": 2.665, "end": 2.926, "score": 0.968}, {"word": "and", "start": 3.007, "end": 3.127, "score": 0.867}, {"word": "a", "start": 3.69, "end": 3.751, "score": 0.893}, {"word": "short", "start": 3.771, "end": 3.972, "score": 0.7}, {"word": "introduction,", "start": 4.032, "end": 4.736, "score": 0.826}, {"word": "like,", "start": 4.796, "end": 4.997, "score": 0.936}, {"word": "what", "start": 5.902, "end": 6.023, "score": 0.874}, {"word": "are", "start": 6.063, "end": 6.143, "score": 0.831}, {"word": "you", "start": 6.224, "end": 6.324, "score": 0.997}, {"word": "doing?", "start": 6.385, "end": 6.686, "score": 0.869}]}, {"start": 6.726, "end": 9.481, "text": "I'm Caroline Levine.", "words": [{"word": "I'm", "start": 6.726, "end": 8.455, "score": 0.959}, {"word": "Caroline", "start": 8.596, "end": 9.059, "score": 0.895}, {"word": "Levine.", "start": 9.099, "end": 9.481, "score": 0.861}]}, {"start": 9.521, "end": 13.703, "text": "I'm professor of humanities at Cornell University.", "words": [{"word": "I'm", "start": 9.521, "end": 10.004, "score": 0.848}, {"word": "professor", "start": 10.164, "end": 10.768, "score": 0.936}, {"word": "of", "start": 11.23, "end": 11.351, "score": 0.847}, {"word": "humanities", "start": 11.693, "end": 12.376, "score": 0.89}, {"word": "at", "start": 12.416, "end": 12.577, "score": 0.776}, {"word": "Cornell", "start": 12.658, "end": 13.02, "score": 0.711}, {"word": "University.", "start": 13.12, "end": 13.703, "score": 0.828}]}], "word_segments": [{"word": "So", "start": 0.031, "end": 0.493, "score": 0.743}, {"word": "first,", "start": 0.574, "end": 0.916, "score": 0.807}, {"word": "if", "start": 0.936, "end": 1.016, "score": 0.57}, {"word": "you", "start": 1.077, "end": 1.278, "score": 0.935}, {"word": "just", "start": 1.358, "end": 1.539, "score": 0.764}, {"word": "want", "start": 1.579, "end": 1.8, "score": 0.886}, {"word": "to", "start": 1.841, "end": 1.961, "score": 0.774}, {"word": "tell", "start": 2.102, "end": 2.303, "score": 0.988}, {"word": "me", "start": 2.343, "end": 2.444, "score": 0.655}, {"word": "your", "start": 2.484, "end": 2.605, "score": 0.91}, {"word": "name", "start": 2.665, "end": 2.926, "score": 0.968}, {"word": "and", "start": 3.007, "end": 3.127, "score": 0.867}, {"word": "a", "start": 3.69, "end": 3.751, "score": 0.893}, {"word": "short", "start": 3.771, "end": 3.972, "score": 0.7}, {"word": "introduction,", "start": 4.032, "end": 4.736, "score": 0.826}, {"word": "like,", "start": 4.796, "end": 4.997, "score": 0.936}, {"word": "what", "start": 5.902, "end": 6.023, "score": 0.874}, {"word": "are", "start": 6.063, "end": 6.143, "score": 0.831}, {"word": "you", "start": 6.224, "end": 6.324, "score": 0.997}, {"word": "doing?", "start": 6.385, "end": 6.686, "score": 0.869}, {"word": "I'm", "start": 6.726, "end": 8.455, "score": 0.959}, {"word": "Caroline", "start": 8.596, "end": 9.059, "score": 0.895}, {"word": "Levine.", "start": 9.099, "end": 9.481, "score": 0.861}, {"word": "I'm", "start": 9.521, "end": 10.004, "score": 0.848}, {"word": "professor", "start": 10.164, "end": 10.768, "score": 0.936}, {"word": "of", "start": 11.23, "end": 11.351, "score": 0.847}, {"word": "humanities", "start": 11.693, "end": 12.376, "score": 0.89}, {"word": "at", "start": 12.416, "end": 12.577, "score": 0.776}, {"word": "Cornell", "start": 12.658, "end": 13.02, "score": 0.711}, {"word": "University.", "start": 13.12, "end": 13.703, "score": 0.828}], "language": "en"} \ No newline at end of file diff --git a/Interview1.srt b/Interview1.srt new file mode 100644 index 0000000..0dbfc34 --- /dev/null +++ b/Interview1.srt @@ -0,0 +1,12 @@ +1 +00:00:00,031 --> 00:00:06,686 +So first, if you just want to tell me your name and a short introduction, like, what are you doing? + +2 +00:00:06,726 --> 00:00:09,481 +I'm Caroline Levine. + +3 +00:00:09,521 --> 00:00:13,703 +I'm professor of humanities at Cornell University. + diff --git a/Interview1.tsv b/Interview1.tsv new file mode 100644 index 0000000..c1f8e3e --- /dev/null +++ b/Interview1.tsv @@ -0,0 +1,4 @@ +start end text +31 6686 So first, if you just want to tell me your name and a short introduction, like, what are you doing? +6726 9481 I'm Caroline Levine. +9521 13703 I'm professor of humanities at Cornell University. diff --git a/Interview1.txt b/Interview1.txt new file mode 100644 index 0000000..b470d77 --- /dev/null +++ b/Interview1.txt @@ -0,0 +1,3 @@ +So first, if you just want to tell me your name and a short introduction, like, what are you doing? +I'm Caroline Levine. +I'm professor of humanities at Cornell University. diff --git a/Interview1.vtt b/Interview1.vtt new file mode 100644 index 0000000..1b70fad --- /dev/null +++ b/Interview1.vtt @@ -0,0 +1,11 @@ +WEBVTT + +00:00.031 --> 00:06.686 +So first, if you just want to tell me your name and a short introduction, like, what are you doing? + +00:06.726 --> 00:09.481 +I'm Caroline Levine. + +00:09.521 --> 00:13.703 +I'm professor of humanities at Cornell University. + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..bc9ed43 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +xmltodict +json +lxml +pytest \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..45ee0ae --- /dev/null +++ b/setup.py @@ -0,0 +1,21 @@ +from setuptools import setup, find_packages + +setup( + name='hindenburg-python-api', + version='0.1.0', + author='Your Name', + author_email='your.email@example.com', + description='A Python API for modifying Hindenburg project files and managing audio transcriptions.', + packages=find_packages(where='src'), + package_dir={'': 'src'}, + install_requires=[ + 'lxml', # For XML handling + 'jsonschema', # For JSON validation + ], + classifiers=[ + 'Programming Language :: Python :: 3', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + ], + python_requires='>=3.6', +) \ No newline at end of file diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/examples/add_transcription.py b/src/examples/add_transcription.py new file mode 100644 index 0000000..ab50325 --- /dev/null +++ b/src/examples/add_transcription.py @@ -0,0 +1,33 @@ +from hindenburg_api.transcription import Transcription +from hindenburg_api.project import Project + +def add_transcription_to_project(project_file_path, audio_file_path, audio_file_id): + # Load the project + project = Project(project_file_path) + project.load_project() + + # Import the transcribe function + from hindenburg_api.transcription import transcribe + + # Run transcription on the audio file + transcription_result = transcribe(audio_file_path) + + # Create a Transcription object + transcription = Transcription() + + # Add each segment from the transcription result + transcription.add_segments(transcription_result) + + # Convert the transcription to XML format and add it to the project + transcription_xml = transcription.to_xml(audio_file_id) + project.add_transcription(audio_file_id, transcription_xml) + + # Save the modified project + project.save_project() + +if __name__ == "__main__": + project_file = "tests/demo_project/demo.nhsx" + audio_file = 'tests/demo_project/demo Files/Interview1.wav' + audio_file_id = "1" # Change this to the appropriate audio file ID + + add_transcription_to_project(project_file, audio_file, audio_file_id) \ No newline at end of file diff --git a/src/examples/modify_clip_color.py b/src/examples/modify_clip_color.py new file mode 100644 index 0000000..65f8e81 --- /dev/null +++ b/src/examples/modify_clip_color.py @@ -0,0 +1,30 @@ +# This file provides an example script demonstrating how to use the API to modify the color of a clip in the project. + +from hindenburg_api.project import Project + +def modify_clip_color(project_file, track_name, region_name, new_color): + # Load the project + project = Project(project_file) + project.load() + + # Find the specified track and region + track = project.get_track(track_name) + if track is None: + print(f"Track '{track_name}' not found.") + return + + region = track.get_region(region_name) + if region is None: + print(f"Region '{region_name}' not found in track '{track_name}'.") + return + + # Modify the clip color + region.colour = new_color + print(f"Changed color of '{region_name}' to '{new_color}'.") + + # Save the project + project.save() + +if __name__ == "__main__": + # Example usage + modify_clip_color("path/to/your/project.nhsx", "Track 2", "Interview1", "red") \ No newline at end of file diff --git a/src/hindenburg_api/__init__.py b/src/hindenburg_api/__init__.py new file mode 100644 index 0000000..7371b1b --- /dev/null +++ b/src/hindenburg_api/__init__.py @@ -0,0 +1 @@ +# This file initializes the hindenburg_api package. \ No newline at end of file diff --git a/src/hindenburg_api/audio.py b/src/hindenburg_api/audio.py new file mode 100644 index 0000000..a2aaa52 --- /dev/null +++ b/src/hindenburg_api/audio.py @@ -0,0 +1,31 @@ +class Audio: + def __init__(self, file_path): + self.file_path = file_path + self.metadata = self.load_audio_metadata() + + def load_audio_metadata(self): + # Logic to load audio file metadata + pass + + def get_duration(self): + # Logic to retrieve the duration of the audio file + pass + + def get_channels(self): + # Logic to retrieve the number of channels in the audio file + pass + + def get_leq(self): + # Logic to retrieve the Leq value of the audio file + pass + + def get_dyn(self): + # Logic to retrieve the dynamic range of the audio file + pass + + def set_metadata(self, metadata): + # Logic to set or update metadata for the audio file + pass + + def __repr__(self): + return f"