Skip to content

Commit

Permalink
Merge pull request #1 from Dayonixe/All_in_one
Browse files Browse the repository at this point in the history
First all-in-one GUI version
  • Loading branch information
Dayonixe authored Mar 9, 2024
2 parents b8d73d3 + 7cf68e4 commit f68d3d1
Show file tree
Hide file tree
Showing 6 changed files with 460 additions and 61 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
.idea/
output/
src/__pycache__/
build/
dist/
application.spec
.env
*.mkv
*.mp4
*.mp3
91 changes: 67 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,12 @@ You'll need to create a `.env` file with the `API_KEY` variable with your API ke
The script on the repository is configured in French; it's easy to modify the script to adapt it to another language (just translate the few instructions given to GPT).

> [!IMPORTANT]
> To use the script, you need to be connected to the Internet so that it can call the OpenAI API.
> To use the application, you need to be connected to the Internet so that it can call the OpenAI API.
You will also need to install the necessary :
```bash
sudo apt-get update

sudo apt install python3
sudo apt install python3-pip
## Installation

pip3 install openai
pip3 install python-docx
pip3 install pydub
```
> [!IMPORTANT]
> For the application to work properly, the `.env` file must be in the same place as the executable.
> [!NOTE]
> For information, the code has been developed and works with the following library versions:
Expand All @@ -37,32 +30,80 @@ pip3 install pydub
> | openai | 0.28.0 |
> | python-docx | 1.1.0 |
> | pydub | 0.25.1 |
> | ffmpeg-python | 0.2.0 |
> | customtkinter | 4.0.2 |
## Usage
### Linux

You will also need to install the necessary :
```bash
python3 src/meetingMinutes.py file_name.mp3
```
sudo apt-get update && sudo apt-get upgrade -y

> [!WARNING]
> The file must be a `.mp3`.
sudo apt install ffmpeg
sudo apt install python3
sudo apt install python3-pip

pip3 install customtkinter
pip3 install python-dotenv
pip3 install openai==0.28
pip3 install pydub
pip3 install python-docx
pip3 install ffmpeg-python
```

When you run the script, it will ask you whether you want to run the complete script or just the transcript.
The complete script includes transcript, summary, key points and action points.
### Windows

1. Download and install [Python](https://www.python.org/)
2. Check that Python is installed using the following command: `python --version` or `python3 --version`.
3. Download and install [FFmpeg](https://ffmpeg.org/)
- Extract the downloaded file to a location of your choice on your computer (somewhere easy to access).
4. To add FFmpeg to the Windows PATH, follow these steps:
- Open System Properties: Right-click on the Start button and choose "*System*". Then click on "*Advanced system settings*" on the left-hand side of the window (on Windows 10) or below the "*Device specifications*" section (on Windows 11).
- Environment variables: In the "*Advanced*" tab, click the "*Environment Variables...*" button at the bottom.
- Edit PATH: Under "*System variables*", find the `Path` variable and click on it, then click on "*Edit...*". If you are running Windows 10 or Windows 11, this will open a window with a list of paths.
- Add the FFmpeg path: Click "*New*" and type the path to the bin folder of your FFmpeg installation. If you extracted FFmpeg to `C:\FFmpeg`, the path to add will probably be `C:\FFmpeg\bin`.
- Save and close: Click "*OK*" to close the `Path` editing window, then "*OK*" again to close the "*Environment Variables*" window and "*OK*" once more to close the "*System Properties*".
5. Check that FFmpeg is installed using the following command: `ffmpeg -version`.
- This command should display the version of FFmpeg installed and the configuration. If you get an error message saying that FFmpeg is not recognised as an internal or external command, this means that FFmpeg has not been correctly added to the PATH or that the terminal needs to be restarted.

You can change the model used in the code by modifying the `model_gpt` variable. You can find a list of the different GPT models supported on the [OpenAI site](https://platform.openai.com/docs/guides/function-calling), along with the methods of use for API calls.
You will also need to install the necessary :
```shell
pip install customtkinter
pip install python-dotenv
pip install openai==0.28
pip install pydub
pip install python-docx
pip install ffmpeg-python
```

## Build

If you get the error that the `mp3` file is too heavy, feel free to cut it to make it lighter with the following commands:
You can build the application using `PyInstaller`.

```bash
# Tool installation
sudo apt install ffmpeg
pip install pyinstaller

# Using ffmpeg
ffmpeg -i test.mp3 -ss 00:00:30 -to 00:10:00 -c copy output.mp3 # Here from 30 seconds to 10 minutes
pyinstaller --onefile --noconsole src/application.py
```

## Usage

Double-click on the executable or use `./application[.exe]` from the command line.

<img src="img/application.png" alt="application" />

The only mandatory cells are "*File browser*" and "*Transcription*", the others such as "*File names*" and "*Start/End Times*" are optional and will have a default value.

The "*File browser*" section is mandatory and takes as input the file to which the application will be applied.
> [!WARNING]
> The file must be a `.mp3` or `.mkv`.
The "*File names*" section is optional and can take as input the name of the output audio and text files. The default values are "*audio_[date]*" and "*meeting_minutes_[date]*".

The "*Start/End Times*" section is optional and can take as input the start and end times of the output audio file. By default, their respective values are "*00:00:00*" and "*[file_end_value]*". The input must be in the format `[00-59]:[00-59]:[00-59]`.

The "*Transcription*" section is mandatory and offers a choice between "*Transcription only*", which will output a text file with only the transcription of the input audio file, or "*Full execution*", which will output a text file with the transcription of the input audio file as well as a summary, a list of key points and a list of action items.

## Performance

Here are the performances I've seen in use:
Expand All @@ -79,6 +120,8 @@ Here are the performances I've seen in use:
| gpt-4-1106-preview | 18min35 | 3min28 | 0.22$ |
| gpt-4 | 27min17 | 4min15 | 0.79$ |

You can change the model used in the code by modifying the `model_gpt` variable. You can find a list of the different GPT models supported on the [OpenAI website](https://platform.openai.com/docs/guides/function-calling), along with the methods of use for API calls.

You can find costs for the various models (including Whisper and GPT-4) on the [OpenAI website](https://openai.com/pricing).

You can also find all your consumption for the current month, as well as your payment history, on the [Usage page](https://platform.openai.com/usage).
Binary file added img/application.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
255 changes: 255 additions & 0 deletions src/application.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
import re
import shutil
import datetime

import customtkinter
from customtkinter import filedialog

from meetingMinutes import meeting_minutes_main
from convertMKVtoMP3 import convert_mkv_to_mp3, cutting_mp3


class MyFileDialogFrame(customtkinter.CTkFrame):
"""
Make a frame to select a file.
"""

def __init__(self, master, title, placeholder, button_text):
"""
Initialise and configure the frame to select a file.
:param master:
:param title: Frame title
:param placeholder: Text in the placeholder
:param button_text: Text in the button
"""
super().__init__(master)

# Display configuration
self.grid_columnconfigure(0, weight=5)

self.title = title
self.placeholder = placeholder
self.button_text = button_text

self.title = customtkinter.CTkLabel(self, text=self.title, fg_color="gray30", corner_radius=6)
self.title.grid(row=0, column=0, padx=10, pady=10, sticky="ew", columnspan=2)

self.entry_placeholder = customtkinter.CTkEntry(self, placeholder_text=self.placeholder)
self.entry_placeholder.grid(row=1, column=0, padx=(10, 0), pady=(0, 10), sticky="ew")
self.entry_placeholder.configure(state="disabled")

# Button configuration
button = customtkinter.CTkButton(self, text=self.button_text, command=self.select_file)
button.grid(row=1, column=1, padx=10, pady=(0, 10), sticky="ew")

def select_file(self):
"""
Selecting a file.
"""
filename = filedialog.askopenfilename(
title='Open a file',
initialdir='.',
filetypes=(
('mkv & mp3 files', '*.mkv *.mp3'),
('All files', '*.*')))

if filename:
self.entry_placeholder.configure(state="normal")
self.entry_placeholder.delete(0, "end")
self.entry_placeholder.insert(0, filename)
self.entry_placeholder.configure(state="disabled")

def get(self):
"""
Get the path to the file.
:return: Path to file
"""
return self.entry_placeholder.get()


class MyEntryFrame(customtkinter.CTkFrame):
"""
Make a frame to enter data.
"""

def __init__(self, master, title, data_titles, placeholders):
"""
Initialise and configure the frame to enter data.
:param master:
:param title: Frame title
:param data_titles: Text in front of entry cells
:param placeholders: Text in the placeholder
"""
super().__init__(master)

# Display configuration
self.grid_columnconfigure(0, weight=0)
self.grid_columnconfigure(1, weight=1)

self.title = title
self.data_titles = data_titles
self.placeholders = placeholders
self.datas = []

self.title = customtkinter.CTkLabel(self, text=self.title, fg_color="gray30", corner_radius=6)
self.title.grid(row=0, column=0, padx=10, pady=10, sticky="ew", columnspan=2)

for i, (data_title, placeholder) in enumerate(zip(self.data_titles, self.placeholders)):
entry_data_title = customtkinter.CTkLabel(self, text=data_title)
entry_data_title.grid(row=i + 1, column=0, padx=10, pady=(0, 10), sticky="w")
entry_placeholder = customtkinter.CTkEntry(self, placeholder_text=placeholder)
entry_placeholder.grid(row=i + 1, column=1, padx=10, pady=(0, 10), sticky="ew")
self.datas.append(entry_placeholder)

def get(self):
"""
Get cell values.
:return: Cell values
"""
data_list = []
for data in self.datas:
data_list.append(data.get())
return data_list


class MyRadioButtonFrame(customtkinter.CTkFrame):
"""
Make a frame to select a radio button value.
"""

def __init__(self, master, title, options):
"""
Initialise and configure the frame to select a radio button value.
:param master:
:param title: Frame title
:param options: Radio button options
"""
super().__init__(master)

# Display configuration
self.grid_columnconfigure(0, weight=1)

self.title = title
self.options = options
self.variable = customtkinter.StringVar(value="")

self.title = customtkinter.CTkLabel(self, text=self.title, fg_color="gray30", corner_radius=6)
self.title.grid(row=0, column=0, padx=10, pady=(10, 0), sticky="ew")

for i, value in enumerate(self.options):
radiobutton = customtkinter.CTkRadioButton(self, text=value, value=value, variable=self.variable)
radiobutton.grid(row=i + 1, column=0, padx=10, pady=(10, 0), sticky="w")

def get(self):
"""
Get the value of the selected radio button.
:return: Radio button value
"""
return self.variable.get()

def set(self, value):
"""
Set the value of the selected radio button.
"""
self.variable.set(value)


class App(customtkinter.CTk):
"""
Main application code.
"""

def __init__(self):
"""
Initialise and configure the window.
"""
super().__init__()

# Window configuration
self.title("Meeting Minutes")
self.geometry("600x420")
self.resizable(False, False)

self.grid_columnconfigure((0, 1), weight=1)
self.grid_rowconfigure((0, 1, 2), weight=1)

# Building the grid
self.file_selection_frame = MyFileDialogFrame(self, "File browser", placeholder="File path",
button_text="Choose file")
self.file_selection_frame.grid(row=0, column=0, padx=10, pady=(10, 5), sticky="nsew", columnspan=2)

self.file_name_selection_frame = MyEntryFrame(self, "File names", data_titles=["Audio file:", "Meeting file:"],
placeholders=["audio_[date].mp3", "meeting_minutes_[date].docx"])
self.file_name_selection_frame.grid(row=1, column=0, padx=10, pady=5, sticky="nsew", columnspan=2)

self.timecode_selection_frame = MyEntryFrame(self, "Start/End Times", data_titles=["Start:", "End:"],
placeholders=["00:00:00", "59:59:59"])
self.timecode_selection_frame.grid(row=2, column=0, padx=(10, 5), pady=5, sticky="nsew")
self.radio_button_selection_frame = MyRadioButtonFrame(self, "Transcription",
options=["Transcription only", "Full execution"])
self.radio_button_selection_frame.grid(row=2, column=1, padx=(5, 10), pady=5, sticky="nsew")

self.button = customtkinter.CTkButton(self, text="Run the program", command=self.code_execution)
self.button.grid(row=3, column=0, padx=10, pady=10, sticky="ew", columnspan=2)

# Setup
self.radio_button_selection_frame.set("Transcription only")

def code_execution(self):
"""
Code execution and processing.
"""
# Getting data
start_time = self.timecode_selection_frame.get()[0]
end_time = self.timecode_selection_frame.get()[1]
path = self.file_selection_frame.get()
radio = self.radio_button_selection_frame.get()
name_mp3 = self.file_name_selection_frame.get()[0]
name_docx = self.file_name_selection_frame.get()[1]
new_path = False

# Checks whether the format of the start and end times is correct
motif = r'^([0-5]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])$'
start_time_good = True if re.match(motif, start_time) else False
end_time_good = True if re.match(motif, end_time) else False

# Convert file to mp3
if path.endswith('.mkv'):
path = convert_mkv_to_mp3(path, name_mp3) if name_mp3 else convert_mkv_to_mp3(path)
new_path = True

# Cutting up the file
if path.endswith('.mp3') and (start_time_good or end_time_good):
cut_params = {}
if name_mp3:
cut_params['name_mp3_file'] = name_mp3
if start_time_good:
cut_params['start_time'] = start_time
if end_time_good:
cut_params['end_time'] = end_time

path = cutting_mp3(path, **cut_params)
new_path = True

# Running the MeetingMinutes
if path.endswith('.mp3'):
name_docx = name_docx if name_docx != "" else None
action_type = "Full" if radio == "Full execution" else "Transcription"

meeting_minutes_main(path, action_type, name_docx)

if not new_path:
output_dir = "output"
if name_mp3 == "":
now = datetime.datetime.now()
formatted_date = now.strftime("%Y-%m-%d_%H-%M-%S")
shutil.copy(path, f"{output_dir}/audio_{formatted_date}.mp3")
else:
shutil.copy(path, f"{output_dir}/{name_mp3}.mp3")
elif path != "":
print("Erreur sur le type de fichier")


if __name__ == '__main__':
app = App()
app.mainloop()
Loading

0 comments on commit f68d3d1

Please sign in to comment.