buletomato25 commited on
Commit
776d9ef
·
2 Parent(s): 9b1bff8 ae92151

multi_reset

Browse files
__pycache__/process.cpython-310.pyc CHANGED
Binary files a/__pycache__/process.cpython-310.pyc and b/__pycache__/process.cpython-310.pyc differ
 
__pycache__/transcription.cpython-310.pyc CHANGED
Binary files a/__pycache__/transcription.cpython-310.pyc and b/__pycache__/transcription.cpython-310.pyc differ
 
app.py CHANGED
@@ -13,13 +13,13 @@ transcripter = TranscriptionMaker()
13
  app = Flask(__name__)
14
 
15
  users = []
16
- segments_dir = ""
17
  transcription_text=""
18
  harassment_keywords = [
19
  "バカ", "馬鹿", "アホ", "死ね", "クソ", "うざい",
20
  "きもい", "キモい", "ブス", "デブ", "ハゲ",
21
  "セクハラ", "パワハラ", "モラハラ"
22
  ]
 
23
 
24
  # トップページ(テンプレート: index.html)
25
  @app.route('/')
@@ -32,7 +32,6 @@ def index():
32
  def feedback():
33
  return render_template('feedback.html')
34
 
35
-
36
  # 会話詳細画面(テンプレート: talkDetail.html)
37
  @app.route('/talk_detail', methods=['GET', 'POST'])
38
  def talk_detail():
@@ -53,10 +52,13 @@ def confirm():
53
  def reset_html():
54
  return render_template('reset.html')
55
 
56
- #メンバー削除
57
  @app.route('/reset_member', methods=['GET', 'POST'])
58
  def reset_member():
59
  global users
 
 
 
60
  try:
61
  data = request.get_json()
62
  if not data or "names" not in data:
@@ -92,10 +94,11 @@ def reset_member():
92
  # 書き起こし作成エンドポイント
93
  @app.route('/transcription',methods =['GET','POST'])
94
  def transcription():
95
- global segments_dir
96
  global transcription_text
 
 
97
  try:
98
- transcription_text = transcripter.create_transcription(segments_dir)
99
  print(transcription_text)
100
  with open(transcription_text,'r',encoding='utf-8') as file:
101
  file_content = file.read()
@@ -139,7 +142,7 @@ def analyze():
139
  # 音声アップロード&解析エンドポイント
140
  @app.route('/upload_audio', methods=['POST'])
141
  def upload_audio():
142
- global segments_dir
143
  try:
144
  data = request.get_json()
145
  # name か users のいずれかが必須。どちらも無い場合はエラー
@@ -169,23 +172,27 @@ def upload_audio():
169
  if len(users) > 1:
170
  print("複数人の場合の処理")
171
  matched_times, segments_dir = process.process_multi_audio(reference_paths, audio_path, threshold=0.05)
 
172
  # 各メンバーのrateを計算
173
  total_time = sum(matched_times)
174
  rates = [(time / total_time) * 100 if total_time > 0 else 0 for time in matched_times]
175
  return jsonify({"rates": rates}), 200
176
  else:
177
  matched_time, unmatched_time, segments_dir = process.process_audio(reference_paths[0], audio_path, threshold=0.05)
 
178
  total_time = matched_time + unmatched_time
179
  rate = (matched_time / total_time) * 100 if total_time > 0 else 0
180
  return jsonify({"rate": rate}), 200
181
  except Exception as e:
182
  print("Error in /upload_audio:", str(e))
183
  return jsonify({"error": "サーバーエラー", "details": str(e)}), 500
 
184
  @app.route('/reset', methods=['GET'])
185
  def reset():
186
  global users
187
  users=[]
188
  return 200
 
189
  @app.route('/upload_base_audio', methods=['POST'])
190
  def upload_base_audio():
191
  global users#グローバル変数を編集できるようにする
 
13
  app = Flask(__name__)
14
 
15
  users = []
 
16
  transcription_text=""
17
  harassment_keywords = [
18
  "バカ", "馬鹿", "アホ", "死ね", "クソ", "うざい",
19
  "きもい", "キモい", "ブス", "デブ", "ハゲ",
20
  "セクハラ", "パワハラ", "モラハラ"
21
  ]
22
+ total_audio = ""
23
 
24
  # トップページ(テンプレート: index.html)
25
  @app.route('/')
 
32
  def feedback():
33
  return render_template('feedback.html')
34
 
 
35
  # 会話詳細画面(テンプレート: talkDetail.html)
36
  @app.route('/talk_detail', methods=['GET', 'POST'])
37
  def talk_detail():
 
52
  def reset_html():
53
  return render_template('reset.html')
54
 
55
+ #メンバー削除&累積音声削除
56
  @app.route('/reset_member', methods=['GET', 'POST'])
57
  def reset_member():
58
  global users
59
+ global total_audio
60
+ print(total_audio)
61
+ process.delete_files_in_directory(total_audio)
62
  try:
63
  data = request.get_json()
64
  if not data or "names" not in data:
 
94
  # 書き起こし作成エンドポイント
95
  @app.route('/transcription',methods =['GET','POST'])
96
  def transcription():
 
97
  global transcription_text
98
+ global total_audio
99
+ print(total_audio)
100
  try:
101
+ transcription_text = transcripter.create_transcription(total_audio)
102
  print(transcription_text)
103
  with open(transcription_text,'r',encoding='utf-8') as file:
104
  file_content = file.read()
 
142
  # 音声アップロード&解析エンドポイント
143
  @app.route('/upload_audio', methods=['POST'])
144
  def upload_audio():
145
+ global total_audio
146
  try:
147
  data = request.get_json()
148
  # name か users のいずれかが必須。どちらも無い場合はエラー
 
172
  if len(users) > 1:
173
  print("複数人の場合の処理")
174
  matched_times, segments_dir = process.process_multi_audio(reference_paths, audio_path, threshold=0.05)
175
+ total_audio = transcripter.merge_segments(segments_dir)
176
  # 各メンバーのrateを計算
177
  total_time = sum(matched_times)
178
  rates = [(time / total_time) * 100 if total_time > 0 else 0 for time in matched_times]
179
  return jsonify({"rates": rates}), 200
180
  else:
181
  matched_time, unmatched_time, segments_dir = process.process_audio(reference_paths[0], audio_path, threshold=0.05)
182
+ total_audio = transcripter.merge_segments(segments_dir)
183
  total_time = matched_time + unmatched_time
184
  rate = (matched_time / total_time) * 100 if total_time > 0 else 0
185
  return jsonify({"rate": rate}), 200
186
  except Exception as e:
187
  print("Error in /upload_audio:", str(e))
188
  return jsonify({"error": "サーバーエラー", "details": str(e)}), 500
189
+
190
  @app.route('/reset', methods=['GET'])
191
  def reset():
192
  global users
193
  users=[]
194
  return 200
195
+
196
  @app.route('/upload_base_audio', methods=['POST'])
197
  def upload_base_audio():
198
  global users#グローバル変数を編集できるようにする
process.py CHANGED
@@ -58,15 +58,7 @@ class AudioProcessor():
58
  embedding2 = self.inference(path2)
59
  return float(self.cosine_similarity(embedding1.data.flatten(), embedding2.data.flatten()))
60
 
61
- def generate_random_string(self,length):
62
- letters = string.ascii_letters + string.digits
63
- return ''.join(random.choice(letters) for i in range(length))
64
-
65
- def generate_filename(self,random_length):
66
- random_string = self.generate_random_string(random_length)
67
- current_time = datetime.now().strftime("%Y%m%d%H%M%S")
68
- filename = f"{current_time}_{random_string}.wav"
69
- return filename
70
 
71
  def process_audio(self, reference_path, input_path, output_folder='/tmp/data/matched_segments', seg_duration=1.0, threshold=0.5):
72
  # 出力先ディレクトリの中身をクリアする
@@ -182,4 +174,17 @@ class AudioProcessor():
182
  print(f"File Not Found Error: {e}")
183
  except Exception as e:
184
  print(f"Unexpected Error: {e}")
185
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  embedding2 = self.inference(path2)
59
  return float(self.cosine_similarity(embedding1.data.flatten(), embedding2.data.flatten()))
60
 
61
+
 
 
 
 
 
 
 
 
62
 
63
  def process_audio(self, reference_path, input_path, output_folder='/tmp/data/matched_segments', seg_duration=1.0, threshold=0.5):
64
  # 出力先ディレクトリの中身をクリアする
 
174
  print(f"File Not Found Error: {e}")
175
  except Exception as e:
176
  print(f"Unexpected Error: {e}")
177
+ return None
178
+
179
+ def delete_files_in_directory(self,directory_path):
180
+ try:
181
+ # ディレクトリ内のすべてのファイルを取得
182
+ for filename in os.listdir(directory_path):
183
+ file_path = os.path.join(directory_path, filename)
184
+ # ファイルのみ削除する
185
+ if os.path.isfile(file_path):
186
+ os.remove(file_path)
187
+ print(f"{file_path} を削除しました")
188
+ except Exception as e:
189
+ print(f"エラーが発生しました: {e}")
190
+
static/menu.js ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function showUserRegister() {
2
+ fetch("/reset");
3
+ window.location.href = "userregister";
4
+ }
5
+ function showRecorder() {
6
+ window.location.href = "index";
7
+ }
8
+ function showResults() {
9
+ window.location.href = "feedback";
10
+ }
11
+ function showTalkDetail() {
12
+ window.location.href = "talk_detail";
13
+ }
14
+ function resetAction() {
15
+ window.location.href = "reset_html";
16
+ }
17
+
18
+ // メニューの表示・非表示
19
+ function toggleMenu(event) {
20
+ event.stopPropagation();
21
+ const menu = document.getElementById("menu");
22
+ menu.classList.toggle("open");
23
+ }
24
+
25
+ function closeMenu(event) {
26
+ const menu = document.getElementById("menu");
27
+ if (
28
+ menu.classList.contains("open") &&
29
+ !menu.contains(event.target) &&
30
+ !event.target.closest("#menuButton")
31
+ ) {
32
+ menu.classList.remove("open");
33
+ }
34
+ }
static/process.js CHANGED
@@ -1,265 +1,222 @@
1
- let isRecording = false;
2
- let mediaRecorder;
3
- let audioChunks = [];
4
- let recordingInterval;
5
- let count_voice = 0;
6
- let before_rate = [];
7
- const RECORDING_INTERVAL_MS = 5000; // 5秒
8
- // メンバーとチャートの初期化
9
- let members = [];
10
- let voiceData = [];
11
- let baseMemberColors = [
12
- "#4caf50",
13
- "#007bff",
14
- "#ffc107",
15
- "#dc3545",
16
- "#28a745",
17
- "#9c27b0",
18
- "#ff9800",
19
- ];
20
- // Chart.js の初期化
21
- const ctx = document.getElementById("speechChart").getContext("2d");
22
- const speechChart = new Chart(ctx, {
23
- type: "doughnut",
24
- data: {
25
- labels: members,
26
- datasets: [
27
- {
28
- data: voiceData,
29
- backgroundColor: getMemberColors(members.length),
30
- },
31
- ],
32
- },
33
- options: {
34
- responsive: true,
35
- plugins: {
36
- legend: {
37
- display: true,
38
- position: "bottom",
39
- labels: { color: "white" },
40
- },
41
- },
42
- },
43
- });
44
- // サーバーからメンバー情報を取得してチャートを更新する関数
45
- async function updateChartFrom() {
46
- try {
47
- const response = await fetch("/confirm");
48
- if (!response.ok) {
49
- throw new Error(`HTTP error! status: ${response.status}`);
50
- }
51
- const data = await response.json();
52
- if (!data || !data.members || !Array.isArray(data.members)) {
53
- console.error("Invalid member data received:", data);
54
- members = ["member1"];
55
- voiceData = [50, 50];
56
- updateChart();
57
- return;
58
- }
59
- members = data.members;
60
- voiceData = [];
61
- for (let i = 0; i < members.length; i++) {
62
- voiceData.push(100 / members.length);
63
- }
64
- updateChart();
65
- } catch (error) {
66
- console.error("Failed to fetch member data:", error);
67
- members = ["member1"];
68
- voiceData = [50, 50];
69
- updateChart();
70
- }
71
- }
72
- function updateChart() {
73
- // 一人モードの場合は、ユーザーとグレー(無音)の比率をチャートに表示
74
- if (members.length === 1) {
75
- const userName = members[0];
76
- speechChart.data.labels = [userName, "無音"];
77
- speechChart.data.datasets[0].backgroundColor = ["#4caf50", "#757575"];
78
- } else {
79
- // 複数メンバーの場合は通常通りの処理
80
- speechChart.data.labels = members;
81
- speechChart.data.datasets[0].backgroundColor = getMemberColors(
82
- members.length
83
- );
84
- }
85
- speechChart.data.datasets[0].data = voiceData;
86
- speechChart.update();
87
- }
88
- // メニューの表示・非表示
89
- function toggleMenu(event) {
90
- event.stopPropagation();
91
- const menu = document.getElementById("menu");
92
- menu.classList.toggle("open");
93
- }
94
-
95
- function closeMenu(event) {
96
- const menu = document.getElementById("menu");
97
- if (
98
- menu.classList.contains("open") &&
99
- !menu.contains(event.target) &&
100
- !event.target.closest("#menuButton")
101
- ) {
102
- menu.classList.remove("open");
103
- }
104
- }
105
-
106
- // リセットボタンの処理
107
- function resetAction() {
108
- window.location.href = "reset_html";
109
- }
110
-
111
- // ページ読み込み時にチャート情報を更新
112
- updateChartFrom();
113
- // 録音ボタンの録音開始/停止処理
114
- async function toggleRecording() {
115
- const recordButton = document.getElementById("recordButton");
116
- if (!isRecording) {
117
- // 録音開始
118
- isRecording = true;
119
- recordButton.classList.add("recording");
120
- try {
121
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
122
- mediaRecorder = new MediaRecorder(stream);
123
- audioChunks = [];
124
- mediaRecorder.ondataavailable = (event) => {
125
- if (event.data.size > 0) {
126
- audioChunks.push(event.data);
127
- }
128
- };
129
- mediaRecorder.onstop = () => {
130
- sendAudioChunks([...audioChunks]);
131
- audioChunks = [];
132
- };
133
- mediaRecorder.start();
134
- // 5秒ごとに録音を停止して送信するインターバルを設定
135
- recordingInterval = setInterval(() => {
136
- if (mediaRecorder && mediaRecorder.state === "recording") {
137
- mediaRecorder.stop();
138
- mediaRecorder.start();
139
- }
140
- }, RECORDING_INTERVAL_MS);
141
- } catch (error) {
142
- console.error("マイクへのアクセスに失敗しました:", error);
143
- isRecording = false;
144
- recordButton.classList.remove("recording");
145
- }
146
- } else {
147
- // 録音停止
148
- isRecording = false;
149
- recordButton.classList.remove("recording");
150
- if (mediaRecorder && mediaRecorder.state === "recording") {
151
- mediaRecorder.stop();
152
- }
153
- clearInterval(recordingInterval);
154
- count_voice = 0;
155
- //before_rate = [];
156
- }
157
- }
158
- function sendAudioChunks(chunks) {
159
- const audioBlob = new Blob(chunks, { type: "audio/wav" });
160
- const reader = new FileReader();
161
- reader.onloadend = () => {
162
- const base64String = reader.result.split(",")[1];
163
- const form = document.getElementById("recordForm");
164
- const nameInput = form.querySelector('input[name="name"]');
165
- const name = nameInput ? nameInput.value : "unknown";
166
- fetch("/upload_audio", {
167
- method: "POST",
168
- headers: { "Content-Type": "application/json" },
169
- body: JSON.stringify({ audio_data: base64String, name: name }),
170
- })
171
- .then((response) => response.json())
172
- .then((data) => {
173
- if (data.error) {
174
- alert("エラー: " + data.error);
175
- console.error(data.details);
176
- } else if (data.rate !== undefined) {
177
- updateChartData(data.rate);
178
- } else if (data.rates !== undefined) {
179
- updateChartData(data.rates);
180
- }
181
- })
182
- .catch((error) => {
183
- console.error("エラー:", error);
184
- });
185
- };
186
- reader.readAsDataURL(audioBlob);
187
- }
188
- function getMemberColors(memberCount) {
189
- // 一人モードの場合は特別な処理をしない(updateChartで処理するため)
190
- if (memberCount <= 1) {
191
- return ["#4caf50", "#757575"];
192
- } else {
193
- let colors = [];
194
- for (let i = 0; i < memberCount; i++) {
195
- colors.push(baseMemberColors[i % baseMemberColors.length]);
196
- }
197
- return colors;
198
- }
199
- }
200
- function updateChartData(newRate) {
201
- // 一人モードの時の処理
202
- if (members.length === 1) {
203
- if (count_voice === 0) {
204
- speechChart.data.datasets[0].data = [newRate, 100 - newRate];
205
- before_rate = [newRate];
206
- } else {
207
- // 一人モードでは、過去のデータと現在のデータを加重平均する
208
- let tmp_rate =
209
- (newRate + before_rate[0] * count_voice) / (count_voice + 1);
210
- speechChart.data.datasets[0].data = [tmp_rate, 100 - tmp_rate];
211
- before_rate = [tmp_rate];
212
- }
213
- count_voice++;
214
- // 一人モードでは常に緑色とグレーの組み合わせを使用
215
- speechChart.data.labels = [members[0], "無音"];
216
- speechChart.data.datasets[0].backgroundColor = ["#4caf50", "#757575"];
217
- } else {
218
- console.log(before_rate);
219
- // 複数人モードの処理
220
- if (!Array.isArray(newRate)) {
221
- console.error("newRate is not an array:", newRate);
222
- return;
223
- }
224
- if (newRate.length !== members.length) {
225
- console.error(
226
- "newRate length does not match members length:",
227
- newRate,
228
- members
229
- );
230
- return;
231
- }
232
- let averagedRates = new Array(newRate.length);
233
- for (let i = 0; i < newRate.length; i++) {
234
- let tmp_rate;
235
- if (count_voice === 0) {
236
- // 初回はそのまま
237
- tmp_rate = newRate[i];
238
- } else {
239
- // 2回目以降は、過去の平均値と現在の値を加重平均する
240
- tmp_rate =
241
- (newRate[i] + before_rate[i] * count_voice) / (count_voice + 1);
242
- }
243
- averagedRates[i] = tmp_rate;
244
- }
245
- // before_rateを更新
246
- before_rate = averagedRates;
247
- //グラフに反映
248
- speechChart.data.datasets[0].data = averagedRates;
249
- count_voice++;
250
- speechChart.data.datasets[0].backgroundColor = getMemberColors(
251
- members.length
252
- );
253
- }
254
- speechChart.update();
255
- }
256
- function showTalkdetail() {
257
- window.location.href = "talk_detail";
258
- }
259
- function showResults() {
260
- window.location.href = "feedback";
261
- }
262
- function showUserRegister() {
263
- fetch("/reset");
264
- window.location.href = "userregister";
265
- }
 
1
+
2
+ let isRecording = false;
3
+ let mediaRecorder;
4
+ let audioChunks = [];
5
+ let recordingInterval;
6
+ let count_voice = 0;
7
+ let before_rate = [];
8
+ const RECORDING_INTERVAL_MS = 5000; // 5秒
9
+ // メンバーとチャートの初期化
10
+ let members = [];
11
+ let voiceData = [];
12
+ let baseMemberColors = ["#4caf50", "#007bff", "#ffc107", "#dc3545", "#28a745", "#9c27b0", "#ff9800"];
13
+ // Chart.js の初期化
14
+ const ctx = document.getElementById("speechChart").getContext("2d");
15
+ const speechChart = new Chart(ctx, {
16
+ type: "doughnut",
17
+ data: {
18
+ labels: members,
19
+ datasets: [
20
+ {
21
+ data: voiceData,
22
+ backgroundColor: getMemberColors(members.length),
23
+ },
24
+ ],
25
+ },
26
+ options: {
27
+ responsive: true,
28
+ plugins: {
29
+ legend: {
30
+ display: true,
31
+ position: "bottom",
32
+ labels: { color: "white" },
33
+ },
34
+ },
35
+ },
36
+ });
37
+ // サーバーからメンバー情報を取得してチャートを更新する関数
38
+ async function updateChartFrom() {
39
+ try {
40
+ const response = await fetch("/confirm");
41
+ if (!response.ok) {
42
+ throw new Error(`HTTP error! status: ${response.status}`);
43
+ }
44
+ const data = await response.json();
45
+ if (!data || !data.members || !Array.isArray(data.members)) {
46
+ console.error("Invalid member data received:", data);
47
+ members = ["member1"];
48
+ voiceData = [50, 50];
49
+ updateChart();
50
+ return;
51
+ }
52
+ members = data.members;
53
+ voiceData = [];
54
+ for (let i = 0; i < members.length; i++) {
55
+ voiceData.push(100 / members.length);
56
+ }
57
+ updateChart();
58
+ } catch (error) {
59
+ console.error("Failed to fetch member data:", error);
60
+ members = ["member1"];
61
+ voiceData = [50, 50];
62
+ updateChart();
63
+ }
64
+ }
65
+ function updateChart() {
66
+ // 一人モードの場合は、ユーザーとグレー(無音)の比率をチャートに表示
67
+ if (members.length === 1) {
68
+ const userName = members[0];
69
+ speechChart.data.labels = [userName, "無音"];
70
+ speechChart.data.datasets[0].backgroundColor = ["#4caf50", "#757575"];
71
+ } else {
72
+ // 複数メンバーの場合は通常通りの処理
73
+ speechChart.data.labels = members;
74
+ speechChart.data.datasets[0].backgroundColor = getMemberColors(members.length);
75
+ }
76
+ speechChart.data.datasets[0].data = voiceData;
77
+ speechChart.update();
78
+ }
79
+
80
+ // ページ読み込み時にチャート情報を更新
81
+ updateChartFrom();
82
+ // 録音ボタンの録音開始/停止処理
83
+ async function toggleRecording() {
84
+ const recordButton = document.getElementById("recordButton");
85
+ if (!isRecording) {
86
+ // 録音開始
87
+ isRecording = true;
88
+ recordButton.classList.add("recording");
89
+ try {
90
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
91
+ mediaRecorder = new MediaRecorder(stream);
92
+ audioChunks = [];
93
+ mediaRecorder.ondataavailable = (event) => {
94
+ if (event.data.size > 0) {
95
+ audioChunks.push(event.data);
96
+ }
97
+ };
98
+ mediaRecorder.onstop = () => {
99
+ sendAudioChunks([...audioChunks]);
100
+ audioChunks = [];
101
+ };
102
+ mediaRecorder.start();
103
+ // 5秒ごとに録音を停止して送信するインターバルを設定
104
+ recordingInterval = setInterval(() => {
105
+ if (mediaRecorder && mediaRecorder.state === "recording") {
106
+ mediaRecorder.stop();
107
+ mediaRecorder.start();
108
+ }
109
+ }, RECORDING_INTERVAL_MS);
110
+ } catch (error) {
111
+ console.error("マイクへのアクセスに失敗しました:", error);
112
+ isRecording = false;
113
+ recordButton.classList.remove("recording");
114
+ }
115
+ } else {
116
+ // 録音停止
117
+ isRecording = false;
118
+ recordButton.classList.remove("recording");
119
+ if (mediaRecorder && mediaRecorder.state === "recording") {
120
+ mediaRecorder.stop();
121
+ }
122
+ clearInterval(recordingInterval);
123
+ count_voice = 0;
124
+ //before_rate = [];
125
+ }
126
+ }
127
+ function sendAudioChunks(chunks) {
128
+ const audioBlob = new Blob(chunks, { type: "audio/wav" });
129
+ const reader = new FileReader();
130
+ reader.onloadend = () => {
131
+ const base64String = reader.result.split(",")[1];
132
+ const form = document.getElementById("recordForm");
133
+ const nameInput = form.querySelector('input[name="name"]');
134
+ const name = nameInput ? nameInput.value : "unknown";
135
+ fetch("/upload_audio", {
136
+ method: "POST",
137
+ headers: { "Content-Type": "application/json" },
138
+ body: JSON.stringify({ audio_data: base64String, name: name }),
139
+ })
140
+ .then((response) => response.json())
141
+ .then((data) => {
142
+ if (data.error) {
143
+ alert("エラー: " + data.error);
144
+ console.error(data.details);
145
+ } else if (data.rate !== undefined) {
146
+ updateChartData(data.rate);
147
+ } else if (data.rates !== undefined) {
148
+ updateChartData(data.rates);
149
+ }
150
+ })
151
+ .catch((error) => {
152
+ console.error("エラー:", error);
153
+ });
154
+ };
155
+ reader.readAsDataURL(audioBlob);
156
+ }
157
+ function getMemberColors(memberCount) {
158
+ // 一人モードの場合は特別な処理をしない(updateChartで処理するため)
159
+ if (memberCount <= 1) {
160
+ return ["#4caf50", "#757575"];
161
+ } else {
162
+ let colors = [];
163
+ for (let i = 0; i < memberCount; i++) {
164
+ colors.push(baseMemberColors[i % baseMemberColors.length]);
165
+ }
166
+ return colors;
167
+ }
168
+ }
169
+ function updateChartData(newRate) {
170
+ // 一人モードの時の処理
171
+ if (members.length === 1) {
172
+ if (count_voice === 0) {
173
+ speechChart.data.datasets[0].data = [newRate, 100 - newRate];
174
+ before_rate = [newRate];
175
+ } else {
176
+ // 一人モードでは、過去のデータと現在のデータを���重平均する
177
+ let tmp_rate = (newRate + before_rate[0] * count_voice) / (count_voice + 1);
178
+ speechChart.data.datasets[0].data = [tmp_rate, 100 - tmp_rate];
179
+ before_rate = [tmp_rate];
180
+ }
181
+ count_voice++;
182
+ // 一人モードでは常に緑色とグレーの組み合わせを使用
183
+ speechChart.data.labels = [members[0], "無音"];
184
+ speechChart.data.datasets[0].backgroundColor = ["#4caf50", "#757575"];
185
+ } else {
186
+ console.log(before_rate)
187
+ // 複数人モードの処理
188
+ if (!Array.isArray(newRate)) {
189
+ console.error("newRate is not an array:", newRate);
190
+ return;
191
+ }
192
+ if (newRate.length !== members.length) {
193
+ console.error(
194
+ "newRate length does not match members length:",
195
+ newRate,
196
+ members
197
+ );
198
+ return;
199
+ }
200
+ let averagedRates = new Array(newRate.length);
201
+ for (let i = 0; i < newRate.length; i++) {
202
+ let tmp_rate;
203
+ if (count_voice === 0) {
204
+ // 初回はそのまま
205
+ tmp_rate = newRate[i];
206
+ } else {
207
+ // 2回目以降は、過去の平均値と現在の値を加重平均する
208
+ tmp_rate = (newRate[i] + before_rate[i] * count_voice) / (count_voice + 1);
209
+ }
210
+ averagedRates[i] = tmp_rate;
211
+ }
212
+ // before_rateを更新
213
+ before_rate = averagedRates;
214
+ //グラフに反映
215
+ speechChart.data.datasets[0].data = averagedRates;
216
+ count_voice++;
217
+ speechChart.data.datasets[0].backgroundColor = getMemberColors(
218
+ members.length
219
+ );
220
+ }
221
+ speechChart.update();
222
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/feedback.html CHANGED
@@ -5,184 +5,154 @@
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
  <title>会話フィードバック画面</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
- <script>
9
- tailwind.config = {
10
- darkMode: "class",
11
- };
12
- </script>
13
- <script>
14
- function getMessage(level) {
15
- if (level < 20) return "やばい";
16
- if (level < 40) return "気をつけよう";
17
- if (level < 60) return "まずまずですね";
18
- if (level < 80) return "がんばれあとちょっと";
19
- return "素晴らしい";
20
  }
21
 
22
- async function getTranscription() {
23
- try {
24
- const response = await fetch("/transcription");
25
- if (!response.ok) {
26
- throw new Error("HTTP error! status: ${response.status}");
27
- }
28
- const data = await response.json;
29
- const results = data.response;
30
- } catch (error) {
31
- console.error("Failed to fetch transcription", error);
32
- }
33
  }
34
 
35
- async function getAnalysis() {
36
- try {
37
- await getTranscription();
38
-
39
- const response = await fetch("/analyze");
40
- if (!response.ok) {
41
- throw new Error(`HTTP error! status: ${response.status}`);
42
- }
43
-
44
- const data = await response.json();
45
- const results = data.results;
46
- const analysis = results.deepseek_analysis;
47
-
48
- // 変数に格納
49
- const conversationLevel = analysis.conversationLevel;
50
- const harassmentPresent = analysis.harassmentPresent;
51
- const harassmentType = analysis.harassmentType;
52
- const repetition = analysis.repetition;
53
- const pleasantConversation = analysis.pleasantConversation;
54
- const blameOrHarassment = analysis.blameOrHarassment;
55
-
56
- // コンソールに表示
57
- console.log(
58
- conversationLevel,
59
- harassmentPresent,
60
- harassmentType,
61
- repetition,
62
- pleasantConversation,
63
- blameOrHarassment
64
- );
65
-
66
- // DOMに表示
67
- document.getElementById(
68
- "level"
69
- ).innerText = `会話レベル: ${conversationLevel}`;
70
- document.getElementById(
71
- "Harassment_bool"
72
- ).innerText = `ハラスメントの有無: ${harassmentPresent}`;
73
- document.getElementById(
74
- "Harassment_type"
75
- ).innerText = `ハラスメントの種類: ${harassmentType}`;
76
- document.getElementById(
77
- "Harassment_loop"
78
- ).innerText = `繰り返しの程度: ${repetition}`;
79
- document.getElementById(
80
- "Harassment_comfort"
81
- ).innerText = `会話の心地よさ: ${pleasantConversation}`;
82
- document.getElementById(
83
- "Harassment_volume"
84
- ).innerText = `非難またはハラスメントの程度: ${blameOrHarassment}`;
85
- } catch (error) {
86
- console.error("Failed to fetch analysis data:", error);
87
- }
88
  }
89
 
90
- window.onload = getAnalysis();
 
 
 
 
 
 
 
 
91
 
92
- function showSampleData() {
93
- const level = 73;
94
- const percentages = [80, 50, 60, 100, 30];
95
- const labels = ["項目1", "項目2", "項目3", "項目4", "項目5"];
96
 
97
- const message = getMessage(level);
98
- document.getElementById("level").innerText = `話者Lv: ${level}`;
99
- document.getElementById("message").innerText = message;
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
- const barElements = document.getElementsByClassName("bar-fill");
102
- const labelElements = document.getElementsByClassName("bar-label");
103
- for (let i = 0; i < barElements.length; i++) {
104
- barElements[i].style.width = `${percentages[i]}%`;
105
- labelElements[i].innerText = labels[i];
106
- }
107
  }
108
 
109
- function showRecorder() {
110
- window.location.href = "index";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  }
112
- function showTalkDetail() {
113
- window.location.href = "talk_detail";
 
114
  }
115
- </script>
116
  </head>
117
- <body
118
- class="flex items-center justify-center min-h-screen bg-gray-900 text-white"
119
- >
120
- <div class="p-8 bg-gray-800 rounded-2xl shadow-xl text-center w-96">
121
- <div class="text-3xl font-bold mb-4" id="level">話者Lv:</div>
122
- <div class="text-xl font-semibold mb-6" id="Harassment_bool">
123
- 素晴らしい
124
- </div>
125
- <div class="text-xl font-semibold mb-6" id="Harassment_type">
126
- 素晴らしい
127
- </div>
128
- <div class="text-xl font-semibold mb-6" id="Harassment_loop">
129
- 素晴らしい
130
- </div>
131
- <div class="text-xl font-semibold mb-6" id="Harassment_comfort">
132
- 素晴らしい
133
- </div>
134
- <div class="text-xl font-semibold mb-6" id="Harassment_volume">
135
- 素晴らしい
136
- </div>
137
- <!--
138
- <div class="space-y-2">
139
- <div class="bar-container flex items-center">
140
- <span class="bar-label w-20 mr-2"></span>
141
- <div class="bar w-full h-6 bg-gray-700 rounded-full overflow-hidden">
142
- <div class="bar-fill h-full bg-blue-500"></div>
143
- </div>
144
- </div>
145
- <div class="bar-container flex items-center">
146
- <span class="bar-label w-20 mr-2"></span>
147
- <div class="bar w-full h-6 bg-gray-700 rounded-full overflow-hidden">
148
- <div class="bar-fill h-full bg-orange-400"></div>
149
- </div>
150
- </div>
151
- <div class="bar-container flex items-center">
152
- <span class="bar-label w-20 mr-2"></span>
153
- <div class="bar w-full h-6 bg-gray-700 rounded-full overflow-hidden">
154
- <div class="bar-fill h-full bg-blue-500"></div>
155
- </div>
156
- </div>
157
- <div class="bar-container flex items-center">
158
- <span class="bar-label w-20 mr-2"></span>
159
- <div class="bar w-full h-6 bg-gray-700 rounded-full overflow-hidden">
160
- <div class="bar-fill h-full bg-orange-400"></div>
161
- </div>
162
- </div>
163
- <div class="bar-container flex items-center">
164
- <span class="bar-label w-20 mr-2"></span>
165
- <div class="bar w-full h-6 bg-gray-700 rounded-full overflow-hidden">
166
- <div class="bar-fill h-full bg-red-500"></div>
167
- </div>
168
- </div>
169
- </div>
170
- -->
171
 
172
- <div class="flex justify-center space-x-4 mt-6">
 
173
  <button
174
- onclick="showRecorder()"
175
- class="px-4 py-2 bg-blue-600 rounded-lg hover:bg-blue-700"
 
176
  >
177
- 録音画面を表示
178
  </button>
179
- <button
180
- onclick="showTalkDetail()"
181
- class="px-4 py-2 bg-blue-600 rounded-lg hover:bg-blue-700"
 
 
182
  >
183
- 会話詳細を表示
184
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  </div>
186
  </div>
 
 
187
  </body>
188
  </html>
 
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
  <title>会話フィードバック画面</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://use.fontawesome.com/releases/v5.10.0/js/all.js"></script>
9
+ <style>
10
+ /* Main Container */
11
+ body {
12
+ background: linear-gradient(135deg, #2c3e50, #1f2937);
13
+ display: flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+ min-height: 100vh;
17
+ font-family: "Arial", sans-serif;
18
+ color: #fff;
 
19
  }
20
 
21
+ /* Main Content Wrapper */
22
+ .main-content {
23
+ border: 5px solid rgba(255, 255, 255, 0.2);
24
+ padding: 2rem;
25
+ border-radius: 1rem;
26
+ width: 90%;
27
+ max-width: 500px;
28
+ background-color: rgba(0, 0, 0, 0.3);
29
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.4);
30
+ text-align: center;
 
31
  }
32
 
33
+ /* Title */
34
+ .main-title {
35
+ font-size: 2.5rem;
36
+ font-weight: bold;
37
+ margin-bottom: 1.5rem;
38
+ color: #fff;
39
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  }
41
 
42
+ /* Hamburger Menu Button */
43
+ #menuButton {
44
+ background-color: rgba(255, 255, 255, 0.1);
45
+ border: none;
46
+ border-radius: 50%;
47
+ padding: 0.5rem;
48
+ cursor: pointer;
49
+ transition: background-color 0.2s ease;
50
+ }
51
 
52
+ #menuButton:hover {
53
+ background-color: rgba(255, 255, 255, 0.2);
54
+ }
 
55
 
56
+ /* Hamburger Menu Styles */
57
+ #menu {
58
+ position: absolute;
59
+ top: 0;
60
+ left: 0;
61
+ z-index: 10;
62
+ transform: translateX(-100%);
63
+ visibility: hidden;
64
+ opacity: 0;
65
+ background-color: rgb(31, 41, 55);
66
+ transition: transform 0.3s ease-in-out, visibility 0s 0.3s,
67
+ opacity 0.3s ease-in-out;
68
+ backdrop-filter: blur(10px);
69
+ border-right: 1px solid rgba(255, 255, 255, 0.2);
70
+ }
71
 
72
+ #menu.open {
73
+ transform: translateX(0);
74
+ visibility: visible;
75
+ opacity: 1;
76
+ transition: transform 0.3s ease-in-out, visibility 0s 0s,
77
+ opacity 0.3s ease-in-out;
78
  }
79
 
80
+ #menu button {
81
+ transition: background-color 0.2s ease;
82
+ background-color: rgba(0, 0, 0, 0.1);
83
+ margin: 2px;
84
+ border-radius: 5px;
85
+ display: flex;
86
+ align-items: center;
87
+ justify-content: flex-start;
88
+ gap: 10px;
89
+ padding: 0.75rem 1rem;
90
+ width: 100%;
91
+ text-align: left;
92
+ border: none;
93
+ color: #fff;
94
+ font-size: 1rem;
95
+ cursor: pointer;
96
  }
97
+
98
+ #menu button:hover {
99
+ background-color: rgba(55, 65, 81, 0.7);
100
  }
101
+ </style>
102
  </head>
103
+ <body>
104
+ <div class="main-content relative">
105
+ <!-- Title -->
106
+ <div class="main-title">会話フィードバック</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
+ <!-- Hamburger Menu -->
109
+ <div class="absolute top-4 left-4">
110
  <button
111
+ id="menuButton"
112
+ class="text-white text-2xl focus:outline-none"
113
+ onclick="toggleMenu(event)"
114
  >
115
+ <i class="fas fa-bars"></i>
116
  </button>
117
+
118
+ <!-- Menu Content -->
119
+ <div
120
+ id="menu"
121
+ class="absolute top-0 left-0 h-full w-64 bg-gray-800 text-white transform -translate-x-full transition-transform duration-300 ease-in-out opacity-0 visibility-hidden"
122
  >
123
+ <div class="px-4 py-2 text-lg font-semibold">メニュー</div>
124
+ <button onclick="showUserRegister()">
125
+ <i class="fas fa-user-plus"></i> メンバーを追加
126
+ </button>
127
+ <button onclick="showRecorder()">
128
+ <i class="fas fa-microphone"></i> 録音画面を表示
129
+ </button>
130
+ <button onclick="showResults()">
131
+ <i class="fas fa-chart-bar"></i> フィードバックを表示
132
+ </button>
133
+ <button onclick="showTalkDetail()">
134
+ <i class="fas fa-comments"></i> 会話詳細を表示
135
+ </button>
136
+ <button onclick="resetAction()">
137
+ <i class="fas fa-redo"></i> リセット
138
+ </button>
139
+ <button onclick="toggleMenu(event)">
140
+ <i class="fas fa-times"></i> 閉じる
141
+ </button>
142
+ </div>
143
+ </div>
144
+
145
+ <!-- Feedback Details -->
146
+ <div class="text-xl font-semibold mb-6" id="level">話者Lv:</div>
147
+ <div class="text-lg mb-2" id="Harassment_bool">ハラスメントの有無:</div>
148
+ <div class="text-lg mb-2" id="Harassment_type">ハラスメントの種類:</div>
149
+ <div class="text-lg mb-2" id="Harassment_loop">繰り返しの程度:</div>
150
+ <div class="text-lg mb-2" id="Harassment_comfort">会話の心地よさ:</div>
151
+ <div class="text-lg mb-2" id="Harassment_volume">
152
+ 非難またはハラスメントの程度:
153
  </div>
154
  </div>
155
+
156
+ <script src="{{ url_for('static', filename='menu.js') }}"></script>
157
  </body>
158
  </html>
templates/index.html CHANGED
@@ -1,224 +1,228 @@
1
- <!DOCTYPE html>
2
- <html lang="ja">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>JustTalk - Voice Analysis</title>
7
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
8
- <script src="https://cdn.tailwindcss.com"></script>
9
- <link
10
- rel="stylesheet"
11
- href="https://use.fontawesome.com/releases/v5.10.0/css/all.css"
12
- />
13
- <style>
14
- /* Custom Chart.js Styles */
15
- #speechChart {
16
- background-color: rgba(255, 255, 255, 0.05);
17
- border-radius: 10px;
18
- padding: 10px;
19
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
20
- }
21
-
22
- /* Record Button Styles */
23
- .record-button {
24
- width: 90px;
25
- height: 90px;
26
- background-color: transparent;
27
- border-radius: 50%;
28
- border: 5px solid white;
29
- display: flex;
30
- justify-content: center;
31
- align-items: center;
32
- cursor: pointer;
33
- box-shadow: 0 6px 10px rgba(0, 0, 0, 0.5);
34
- transition: all 0.2s ease;
35
- }
36
-
37
- .record-icon {
38
- width: 70px;
39
- height: 70px;
40
- background-color: #e53e3e;
41
- border-radius: 50%;
42
- transition: all 0.2s ease;
43
- }
44
-
45
- .recording .record-icon {
46
- width: 50px;
47
- height: 50px;
48
- border-radius: 15%;
49
- background-color: #c53030;
50
- }
51
-
52
- .icon i {
53
- font-size: 24px;
54
- }
55
-
56
- /* Hamburger Menu Styles */
57
- #menu {
58
- position: absolute;
59
- top: 0;
60
- left: 0;
61
- z-index: 10;
62
- transform: translateX(-100%);
63
- visibility: hidden;
64
- opacity: 0;
65
- background-color: rgb(31, 41, 55);
66
- transition: transform 0.3s ease-in-out, visibility 0s 0.3s,
67
- opacity 0.3s ease-in-out;
68
- backdrop-filter: blur(10px);
69
- border-right: 1px solid rgba(255, 255, 255, 0.2);
70
- }
71
-
72
- #menu.open {
73
- transform: translateX(0);
74
- visibility: visible;
75
- opacity: 1;
76
- transition: transform 0.3s ease-in-out, visibility 0s 0s,
77
- opacity 0.3s ease-in-out;
78
- }
79
-
80
- #menu button {
81
- transition: background-color 0.2s ease;
82
- background-color: rgba(0, 0, 0, 0.1); /* 変更点: 透明度を 10% に変更 */
83
- margin: 2px;
84
- border-radius: 5px;
85
- display: flex;
86
- align-items: center;
87
- justify-content: flex-start;
88
- gap: 10px;
89
- padding: 0.75rem 1rem;
90
- width: 100%;
91
- text-align: left;
92
- border: none;
93
- color: #fff;
94
- font-size: 1rem;
95
- cursor: pointer;
96
- }
97
-
98
- #menu button:hover {
99
- background-color: rgba(55, 65, 81, 0.7);
100
- }
101
-
102
- /* Responsive Design */
103
- @media (max-width: 640px) {
104
- .w-72 {
105
- width: 95%;
106
- }
107
- .h-72 {
108
- height: 350px;
109
- }
110
- }
111
- /* Main Container */
112
- body {
113
- background: linear-gradient(135deg, #2c3e50, #1f2937);
114
- display: flex;
115
- align-items: center;
116
- justify-content: center;
117
- min-height: 100vh;
118
- font-family: 'Arial', sans-serif;
119
- }
120
-
121
- /* Main Content Wrapper */
122
- .main-content {
123
- border: 5px solid rgba(255, 255, 255, 0.2);
124
- padding: 2rem;
125
- border-radius: 1rem;
126
- width: 90%;
127
- max-width: 500px;
128
- background-color: rgba(0, 0, 0, 0.3);
129
- box-shadow: 0 10px 20px rgba(0, 0, 0, 0.4);
130
- text-align: center;
131
- }
132
-
133
- /* Title */
134
- .main-title {
135
- font-size: 2.5rem;
136
- font-weight: bold;
137
- margin-bottom: 1.5rem;
138
- color: #fff;
139
- text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
140
- }
141
-
142
- /* Hamburger Menu Button */
143
- #menuButton {
144
- background-color: rgba(255, 255, 255, 0.1);
145
- border: none;
146
- border-radius: 50%;
147
- padding: 0.5rem;
148
- cursor: pointer;
149
- transition: background-color 0.2s ease;
150
- }
151
-
152
- #menuButton:hover {
153
- background-color: rgba(255, 255, 255, 0.2);
154
- }
155
- </style>
156
- </head>
157
- <body onclick="closeMenu(event)">
158
- <!-- Main Content Wrapper -->
159
- <div class="main-content relative">
160
- <!-- Title -->
161
- <div class="main-title">JustTalk</div>
162
-
163
- <!-- Hamburger Menu -->
164
- <div class="absolute top-4 left-4">
165
- <button
166
- id="menuButton"
167
- class="text-white text-2xl focus:outline-none"
168
- onclick="toggleMenu(event)"
169
- >
170
- <i class="fas fa-bars"></i>
171
- </button>
172
-
173
- <!-- Menu Content -->
174
- <div
175
- id="menu"
176
- class="absolute top-0 left-0 h-full w-64 bg-gray-800 text-white transform -translate-x-full transition-transform duration-300 ease-in-out opacity-0 visibility-hidden"
177
- >
178
- <div class="px-4 py-2 text-lg font-semibold">メニュー</div>
179
- <button onclick="showUserRegister()">
180
- <i class="fas fa-user-plus"></i> メンバーを追加
181
- </button>
182
- <button onclick="showResults()">
183
- <i class="fas fa-chart-bar"></i> フィードバックを表示
184
- </button>
185
- <button onclick="showTalkdetail()">
186
- <i class="fas fa-comments"></i> 会話詳細を表示
187
- </button>
188
- <button onclick="resetAction()">
189
- <i class="fas fa-redo"></i> リセット
190
- </button>
191
- <button onclick="toggleMenu(event)">
192
- <i class="fas fa-times"></i> 閉じる
193
- </button>
194
- </div>
195
- </div>
196
-
197
- <!-- Chart Display -->
198
- <div class="chart w-72 h-72 mb-5 mx-auto">
199
- <canvas id="speechChart"></canvas>
200
- </div>
201
-
202
- <!-- Record Form -->
203
- <form
204
- id="recordForm"
205
- action="/submit"
206
- method="POST"
207
- class="flex items-center justify-center space-x-2 w-full sm:w-auto"
208
- onsubmit="event.preventDefault();"
209
- >
210
- <!-- Record Button -->
211
- <button
212
- type="button"
213
- class="record-button"
214
- id="recordButton"
215
- onclick="toggleRecording()"
216
- >
217
- <div class="record-icon" id="recordIcon"></div>
218
- </button>
219
- </form>
220
- </div>
221
-
222
- <script src="{{ url_for('static', filename='process.js') }}"></script>
223
- </body>
224
- </html>
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ja">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>JustTalk - Voice Analysis</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <link
10
+ rel="stylesheet"
11
+ href="https://use.fontawesome.com/releases/v5.10.0/css/all.css"
12
+ />
13
+ <style>
14
+ /* Custom Chart.js Styles */
15
+ #speechChart {
16
+ background-color: rgba(255, 255, 255, 0.05);
17
+ border-radius: 10px;
18
+ padding: 10px;
19
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
20
+ }
21
+
22
+ /* Record Button Styles */
23
+ .record-button {
24
+ width: 90px;
25
+ height: 90px;
26
+ background-color: transparent;
27
+ border-radius: 50%;
28
+ border: 5px solid white;
29
+ display: flex;
30
+ justify-content: center;
31
+ align-items: center;
32
+ cursor: pointer;
33
+ box-shadow: 0 6px 10px rgba(0, 0, 0, 0.5);
34
+ transition: all 0.2s ease;
35
+ }
36
+
37
+ .record-icon {
38
+ width: 70px;
39
+ height: 70px;
40
+ background-color: #e53e3e;
41
+ border-radius: 50%;
42
+ transition: all 0.2s ease;
43
+ }
44
+
45
+ .recording .record-icon {
46
+ width: 50px;
47
+ height: 50px;
48
+ border-radius: 15%;
49
+ background-color: #c53030;
50
+ }
51
+
52
+ .icon i {
53
+ font-size: 24px;
54
+ }
55
+
56
+ /* Hamburger Menu Styles */
57
+ #menu {
58
+ position: absolute;
59
+ top: 0;
60
+ left: 0;
61
+ z-index: 10;
62
+ transform: translateX(-100%);
63
+ visibility: hidden;
64
+ opacity: 0;
65
+ background-color: rgb(31, 41, 55);
66
+ transition: transform 0.3s ease-in-out, visibility 0s 0.3s,
67
+ opacity 0.3s ease-in-out;
68
+ backdrop-filter: blur(10px);
69
+ border-right: 1px solid rgba(255, 255, 255, 0.2);
70
+ }
71
+
72
+ #menu.open {
73
+ transform: translateX(0);
74
+ visibility: visible;
75
+ opacity: 1;
76
+ transition: transform 0.3s ease-in-out, visibility 0s 0s,
77
+ opacity 0.3s ease-in-out;
78
+ }
79
+
80
+ #menu button {
81
+ transition: background-color 0.2s ease;
82
+ background-color: rgba(0, 0, 0, 0.1); /* 変更点: 透明度を 10% に変更 */
83
+ margin: 2px;
84
+ border-radius: 5px;
85
+ display: flex;
86
+ align-items: center;
87
+ justify-content: flex-start;
88
+ gap: 10px;
89
+ padding: 0.75rem 1rem;
90
+ width: 100%;
91
+ text-align: left;
92
+ border: none;
93
+ color: #fff;
94
+ font-size: 1rem;
95
+ cursor: pointer;
96
+ }
97
+
98
+ #menu button:hover {
99
+ background-color: rgba(55, 65, 81, 0.7);
100
+ }
101
+
102
+ /* Responsive Design */
103
+ @media (max-width: 640px) {
104
+ .w-72 {
105
+ width: 95%;
106
+ }
107
+ .h-72 {
108
+ height: 350px;
109
+ }
110
+ }
111
+ /* Main Container */
112
+ body {
113
+ background: linear-gradient(135deg, #2c3e50, #1f2937);
114
+ display: flex;
115
+ align-items: center;
116
+ justify-content: center;
117
+ min-height: 100vh;
118
+ font-family: "Arial", sans-serif;
119
+ }
120
+
121
+ /* Main Content Wrapper */
122
+ .main-content {
123
+ border: 5px solid rgba(255, 255, 255, 0.2);
124
+ padding: 2rem;
125
+ border-radius: 1rem;
126
+ width: 90%;
127
+ max-width: 500px;
128
+ background-color: rgba(0, 0, 0, 0.3);
129
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.4);
130
+ text-align: center;
131
+ }
132
+
133
+ /* Title */
134
+ .main-title {
135
+ font-size: 2.5rem;
136
+ font-weight: bold;
137
+ margin-bottom: 1.5rem;
138
+ color: #fff;
139
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
140
+ }
141
+
142
+ /* Hamburger Menu Button */
143
+ #menuButton {
144
+ background-color: rgba(255, 255, 255, 0.1);
145
+ border: none;
146
+ border-radius: 50%;
147
+ padding: 0.5rem;
148
+ cursor: pointer;
149
+ transition: background-color 0.2s ease;
150
+ }
151
+
152
+ #menuButton:hover {
153
+ background-color: rgba(255, 255, 255, 0.2);
154
+ }
155
+ </style>
156
+ </head>
157
+ <body onclick="closeMenu(event)">
158
+ <!-- Main Content Wrapper -->
159
+ <div class="main-content relative">
160
+ <!-- Title -->
161
+ <div class="main-title">JustTalk</div>
162
+
163
+ <!-- Hamburger Menu -->
164
+ <div class="absolute top-4 left-4">
165
+ <button
166
+ id="menuButton"
167
+ class="text-white text-2xl focus:outline-none"
168
+ onclick="toggleMenu(event)"
169
+ >
170
+ <i class="fas fa-bars"></i>
171
+ </button>
172
+
173
+ <!-- Menu Content -->
174
+ <div
175
+ id="menu"
176
+ class="absolute top-0 left-0 h-full w-64 bg-gray-800 text-white transform -translate-x-full transition-transform duration-300 ease-in-out opacity-0 visibility-hidden"
177
+ >
178
+ <div class="px-4 py-2 text-lg font-semibold">メニュー</div>
179
+ <button onclick="showUserRegister()">
180
+ <i class="fas fa-user-plus"></i> メンバーを追加
181
+ </button>
182
+ <button onclick="showRecorder()">
183
+ <i class="fas fa-microphone"></i> 録音画面を表示
184
+ </button>
185
+ <button onclick="showResults()">
186
+ <i class="fas fa-chart-bar"></i> フィードバックを表示
187
+ </button>
188
+ <button onclick="showTalkDetail()">
189
+ <i class="fas fa-comments"></i> 会話詳細を表示
190
+ </button>
191
+ <button onclick="resetAction()">
192
+ <i class="fas fa-redo"></i> リセット
193
+ </button>
194
+ <button onclick="toggleMenu(event)">
195
+ <i class="fas fa-times"></i> 閉じる
196
+ </button>
197
+ </div>
198
+ </div>
199
+
200
+ <!-- Chart Display -->
201
+ <div class="chart w-72 h-72 mb-5 mx-auto">
202
+ <canvas id="speechChart"></canvas>
203
+ </div>
204
+
205
+ <!-- Record Form -->
206
+ <form
207
+ id="recordForm"
208
+ action="/submit"
209
+ method="POST"
210
+ class="flex items-center justify-center space-x-2 w-full sm:w-auto"
211
+ onsubmit="event.preventDefault();"
212
+ >
213
+ <!-- Record Button -->
214
+ <button
215
+ type="button"
216
+ class="record-button"
217
+ id="recordButton"
218
+ onclick="toggleRecording()"
219
+ >
220
+ <div class="record-icon" id="recordIcon"></div>
221
+ </button>
222
+ </form>
223
+ </div>
224
+
225
+ <script src="{{ url_for('static', filename='process.js') }}"></script>
226
+ <script src="{{ url_for('static', filename='menu.js') }}"></script>
227
+ </body>
228
+ </html>
templates/talkDetail.html CHANGED
@@ -43,17 +43,21 @@
43
  if (!response.ok) throw new Error("データ取得に失敗しました。");
44
 
45
  const data = await response.json();
46
- const conversations = data.conversations;
47
 
48
- if (!Array.isArray(conversations)) {
49
  throw new Error("会話データが見つかりませんでした。");
50
  }
 
51
  const formattedText = conversations
52
 
53
  .map((conv) => `【${conv.speaker}】 ${conv.text}`)
54
  .join("\n");
55
  console.log("フォーマットテキスト:", formattedText);
56
  transcriptionElement.textContent = formattedText;
 
 
 
57
  } catch (error) {
58
  transcriptionElement.textContent = `エラー: ${error.message}`;
59
  console.error("データ取得エラー:", error);
 
43
  if (!response.ok) throw new Error("データ取得に失敗しました。");
44
 
45
  const data = await response.json();
46
+ const conversations = data.transcription;
47
 
48
+ if (!data || !data.transcription) {
49
  throw new Error("会話データが見つかりませんでした。");
50
  }
51
+ /*
52
  const formattedText = conversations
53
 
54
  .map((conv) => `【${conv.speaker}】 ${conv.text}`)
55
  .join("\n");
56
  console.log("フォーマットテキスト:", formattedText);
57
  transcriptionElement.textContent = formattedText;
58
+ */
59
+ transcriptionElement.textContent = conversations;
60
+ console.log(conversations);
61
  } catch (error) {
62
  transcriptionElement.textContent = `エラー: ${error.message}`;
63
  console.error("データ取得エラー:", error);
transcription.py CHANGED
@@ -1,6 +1,9 @@
1
  import os
2
  from faster_whisper import WhisperModel
3
  from pydub import AudioSegment
 
 
 
4
 
5
  # Matplotlibのキャッシュディレクトリを変更
6
  os.environ["MPLCONFIGDIR"] = "/tmp/matplotlib"
@@ -18,14 +21,14 @@ class TranscriptionMaker():
18
 
19
 
20
  #音声ファイルのディレクトリを受け取り、書き起こしファイルを作成する
21
- def create_transcription(self,segments_directory):
22
  results = []
23
- #細切れ音声をくっつける
24
- audio_directory = self.merge_segments(segments_directory)
25
  #ディレクトリ内のファイルを全て取得
26
  if not os.path.isdir(audio_directory):
27
  raise ValueError(f"The specified path is not a valid directory: {audio_directory}")
28
  audio_files = os.listdir(audio_directory)
 
29
  for audio_file in audio_files:
30
  if os.path.splitext(audio_file)[-1].lower() != '.wav':
31
  continue
@@ -35,7 +38,8 @@ class TranscriptionMaker():
35
  except Exception as e:
36
  print(f"Error transcripting file {audio_path}: {e}")
37
  raise
38
- for segment in segments:
 
39
  results.append({
40
  "start": segment.start,
41
  "end": segment.end,
@@ -53,7 +57,7 @@ class TranscriptionMaker():
53
  return output_file
54
 
55
  #ファイル名が連続しているならくっつける
56
- def merge_segments(self,segments_dir,output_dir = "/tmp/data/merged_audio"):
57
  if not os.path.exists(output_dir):
58
  os.makedirs(output_dir, exist_ok=True)
59
 
@@ -90,7 +94,16 @@ class TranscriptionMaker():
90
  segment = AudioSegment.from_file(file_path)
91
  combined_audio += segment
92
  # 出力ファイル名を設定して保存
93
- output_file = os.path.join(output_dir, f'merged_{i}.wav')
94
  combined_audio.export(output_file, format='wav')
95
 
96
- return output_dir
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  from faster_whisper import WhisperModel
3
  from pydub import AudioSegment
4
+ import string
5
+ import random
6
+ from datetime import datetime
7
 
8
  # Matplotlibのキャッシュディレクトリを変更
9
  os.environ["MPLCONFIGDIR"] = "/tmp/matplotlib"
 
21
 
22
 
23
  #音声ファイルのディレクトリを受け取り、書き起こしファイルを作成する
24
+ def create_transcription(self,audio_directory):
25
  results = []
26
+
 
27
  #ディレクトリ内のファイルを全て取得
28
  if not os.path.isdir(audio_directory):
29
  raise ValueError(f"The specified path is not a valid directory: {audio_directory}")
30
  audio_files = os.listdir(audio_directory)
31
+ audio_files = sorted(os.listdir(audio_directory))
32
  for audio_file in audio_files:
33
  if os.path.splitext(audio_file)[-1].lower() != '.wav':
34
  continue
 
38
  except Exception as e:
39
  print(f"Error transcripting file {audio_path}: {e}")
40
  raise
41
+ sorted_segments = sorted(segments, key=lambda s: s.start)
42
+ for segment in sorted_segments:
43
  results.append({
44
  "start": segment.start,
45
  "end": segment.end,
 
57
  return output_file
58
 
59
  #ファイル名が連続しているならくっつける
60
+ def merge_segments(self,segments_dir,output_dir = "/tmp/data/merged_segment"):
61
  if not os.path.exists(output_dir):
62
  os.makedirs(output_dir, exist_ok=True)
63
 
 
94
  segment = AudioSegment.from_file(file_path)
95
  combined_audio += segment
96
  # 出力ファイル名を設定して保存
97
+ output_file = os.path.join(output_dir, self.generate_filename(3))
98
  combined_audio.export(output_file, format='wav')
99
 
100
+ return output_dir
101
+
102
+ def generate_random_string(self,length):
103
+ letters = string.ascii_letters + string.digits
104
+ return ''.join(random.choice(letters) for i in range(length))
105
+
106
+ def generate_filename(self,random_length):
107
+ current_time = datetime.now().strftime("%Y%m%d%H%M%S")
108
+ filename = f"{current_time}.wav"
109
+ return filename