├── diarist ├── __init__.py ├── baseline │ ├── __init__.py │ ├── clustering.py │ ├── utils.py │ ├── translate_and_diarize.py │ └── diarize_and_translate.py ├── data │ ├── download_alimeeting_translation.sh │ ├── generate_data_json.py │ ├── generate_diarist_alimeeting.py │ ├── dev.json │ └── test.json └── scoring │ └── eval.py ├── .gitignore ├── setup.py ├── LICENSE.txt ├── run_prepare_data.sh └── README.md /diarist/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /diarist/baseline/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.py[cod] 3 | *$py.class 4 | *.egg-info 5 | .pytest_cache 6 | .idea 7 | .tsv 8 | .json 9 | .wav 10 | pretrained_models -------------------------------------------------------------------------------- /diarist/data/download_alimeeting_translation.sh: -------------------------------------------------------------------------------- 1 | #!/bi/bash 2 | 3 | outdir=$1 4 | 5 | mkdir -p $outdir/AliMeetingTranslation 6 | 7 | curl https://www.microsoft.com/en-us/research/uploads/prod/2023/09/readme.txt -o $outdir/AliMeetingTranslation/readme.txt 8 | curl https://www.microsoft.com/en-us/research/uploads/prod/2023/09/test.txt -o $outdir/AliMeetingTranslation/test.txt 9 | curl https://www.microsoft.com/en-us/research/uploads/prod/2023/09/dev.txt -o $outdir/AliMeetingTranslation/dev.txt 10 | curl https://www.microsoft.com/en-us/research/uploads/prod/2023/09/train.txt -o $outdir/AliMeetingTranslation/train.txt 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name="diarist", 5 | version="0.1", 6 | packages=find_packages(), 7 | install_requires=[ 8 | "numpy", 9 | "torch", 10 | "torchaudio", 11 | "Cython", 12 | "nemo_toolkit[all]", 13 | "speechbrain", 14 | "fire", 15 | "sacrebleu==2.3.1", 16 | ], 17 | entry_points={ 18 | "console_scripts": [ 19 | "diarist_baseline_td=diarist.baseline.translate_and_diarize:main", 20 | "diarist_baseline_dt=diarist.baseline.diarize_and_translate:main", 21 | "diarist_eval=diarist.scoring.eval:main" 22 | ] 23 | }, 24 | ) 25 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Mu Yang, Naoyuki Kanda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE -------------------------------------------------------------------------------- /run_prepare_data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RAW_DATA_DIR=data/raw_data/ 4 | OUTPUT_DIR=data/DiariST-AliMeeting/ 5 | 6 | mkdir -p $RAW_DATA_DIR 7 | mkdir -p $OUTPUT_DIR 8 | 9 | # 10 | # Download AliMeeting 11 | # 12 | if [ ! -f $RAW_DATA_DIR/Eval_Ali.tar.gz ]; then 13 | cd $RAW_DATA_DIR 14 | wget https://speech-lab-share-data.oss-cn-shanghai.aliyuncs.com/AliMeeting/openlr/Eval_Ali.tar.gz 15 | tar -zxvf Eval_Ali.tar.gz 16 | cd - 17 | fi 18 | if [ ! -f $RAW_DATA_DIR/Test_Ali.tar.gz ]; then 19 | cd $RAW_DATA_DIR 20 | wget https://speech-lab-share-data.oss-cn-shanghai.aliyuncs.com/AliMeeting/openlr/Test_Ali.tar.gz 21 | tar -zxvf Test_Ali.tar.gz 22 | cd - 23 | fi 24 | 25 | # 26 | # Download translation 27 | # 28 | if [ ! -d $RAW_DATA_DIR/AliMeetingTranslation ]; then 29 | bash diarist/data/download_alimeeting_translation.sh $RAW_DATA_DIR 30 | fi 31 | 32 | # 33 | # Generate DiariST-AliMeeting 34 | # 35 | for data_type in dev test; do 36 | if [ ! -f $OUTPUT_DIR/$data_type.json ]; then 37 | python diarist/data/generate_data_json.py \ 38 | $RAW_DATA_DIR/AliMeetingTranslation/$data_type.txt \ 39 | diarist/data/$data_type.json \ 40 | > $OUTPUT_DIR/$data_type.json 41 | fi 42 | done 43 | 44 | if [ ! -f data/DiariST-AliMeeting/.done ]; then 45 | python diarist/data/generate_diarist_alimeeting.py $RAW_DATA_DIR || exit 1 46 | touch data/DiariST-AliMeeting/.done 47 | fi -------------------------------------------------------------------------------- /diarist/baseline/clustering.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from nemo.collections.asr.parts.utils.offline_clustering import ( 4 | NMESC, 5 | getCosAffinityMatrix, 6 | getAffinityGraphMat, 7 | SpectralClustering, 8 | ) 9 | 10 | 11 | def clustering( 12 | embeddings, 13 | num_speakers=-1, 14 | max_num_speakers=6, 15 | max_rp_threshold=0.15, 16 | sparse_search=True, 17 | sparse_search_volume=10, 18 | fixed_thres=-1.0, 19 | nme_mat_size=300, 20 | cuda=False, 21 | ): 22 | """NMESC-based clustering""" 23 | mat = getCosAffinityMatrix(embeddings) 24 | nmesc = NMESC( 25 | mat, 26 | max_num_speakers=max_num_speakers, 27 | max_rp_threshold=max_rp_threshold, 28 | sparse_search=sparse_search, 29 | sparse_search_volume=sparse_search_volume, 30 | fixed_thres=fixed_thres, 31 | nme_mat_size=nme_mat_size, 32 | cuda=cuda, 33 | ) 34 | try: 35 | est_num_of_spk, p_hat_value = nmesc.forward() 36 | affinity_mat = getAffinityGraphMat(mat, p_hat_value) 37 | if num_speakers > 0: 38 | est_num_of_spk = num_speakers 39 | spectral_model = SpectralClustering(n_clusters=est_num_of_spk, cuda=cuda) 40 | Y = spectral_model.forward(affinity_mat) 41 | except Exception as err: 42 | print( 43 | "NMESC-based speaker counting failed, which usually happens when there's only 1 segment." 44 | ) 45 | print(embeddings.shape) 46 | print(err) 47 | Y = [0 for _ in range(embeddings.shape[0])] 48 | 49 | return Y 50 | -------------------------------------------------------------------------------- /diarist/data/generate_data_json.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import json 5 | 6 | 7 | if __name__ == "__main__": 8 | in_tsv = sys.argv[1] 9 | in_json = sys.argv[2] 10 | 11 | translation_data = {} 12 | with open(in_tsv, "r") as f: 13 | for line in f: 14 | line = line.strip() 15 | session, speaker, start, end, text, translation = line.split("\t") 16 | if session not in translation_data: 17 | translation_data[session] = [] 18 | translation_data[session].append( 19 | { 20 | "start": float(start), 21 | "end": float(end), 22 | "speaker": speaker, 23 | "text": text, 24 | "translation": translation, 25 | } 26 | ) 27 | 28 | with open(in_json, "r") as f: 29 | data = json.load(f) 30 | 31 | for mini_session in data: 32 | start = mini_session["start"] 33 | end = mini_session["end"] 34 | session = mini_session["session"] 35 | mini_session["data"] = [] 36 | for elem in translation_data[session]: 37 | elem_start = elem["start"] 38 | elem_end = elem["end"] 39 | if start <= elem_start and elem_end <= end: 40 | mini_session["data"].append(elem) 41 | mini_session["data"] = sorted( 42 | mini_session["data"], key=lambda x: x["start"] 43 | ) 44 | print(json.dumps(data, indent=4, ensure_ascii=False)) 45 | -------------------------------------------------------------------------------- /diarist/baseline/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import glob 5 | import re 6 | 7 | 8 | def get_frame(start_sec, end_sec, audio_len, min_dur, sr=16000): 9 | start_fr = min(audio_len, max(0, int(start_sec * sr))) 10 | end_fr = min(audio_len, int(end_sec * sr)) 11 | 12 | need_to_add_fr = int(min_dur * sr) - (end_fr - start_fr) 13 | if need_to_add_fr > 0: 14 | start_fr = max(0, start_fr - int(need_to_add_fr / 2)) 15 | end_fr = min(audio_len, end_fr + int(need_to_add_fr / 2)) 16 | 17 | # edge case 18 | need_to_add_fr = int(min_dur * sr) - (end_fr - start_fr) 19 | if need_to_add_fr > 0: 20 | if start_fr == 0: 21 | end_fr = min(audio_len, end_fr + need_to_add_fr) 22 | elif end_fr == audio_len: 23 | start_fr = max(0, start_fr - need_to_add_fr) 24 | return start_fr, end_fr 25 | 26 | 27 | def get_list(in_wav="", out_tsv="", in_dir="", out_dir="", rank=0, world_size=8): 28 | """get list of input wav and outpu tsv files""" 29 | 30 | in_wav_list = [] 31 | out_tsv_list = [] 32 | if in_wav != "" and out_tsv != "": 33 | in_wav_list = [in_wav] 34 | out_tsv_list = [out_tsv] 35 | elif in_dir != "" and out_dir != "": 36 | in_wav_list = sorted(glob.glob(f"{in_dir}/**/*.wav", recursive=True)) 37 | in_wav_list = [x for i, x in enumerate(in_wav_list) if i % world_size == rank] 38 | 39 | for _in_wav in in_wav_list: 40 | basename = os.path.splitext(re.sub(in_dir, "", _in_wav))[0] 41 | out_tsv_list.append(os.path.join(out_dir, f"{basename}.tsv")) 42 | else: 43 | raise ValueError("in_wav and out_tsv or in_dir and out_dir must be set.") 44 | return in_wav_list, out_tsv_list 45 | 46 | 47 | def dump_result(diar_result, out_tsv): 48 | """dump_result""" 49 | 50 | out_dir = os.path.dirname(out_tsv) 51 | if not os.path.exists(out_dir) and out_dir != "": 52 | os.makedirs(out_dir) 53 | with open(out_tsv, "w", encoding="utf-8") as out_f: 54 | for res in diar_result: 55 | out_f.write(f"{res}\n") 56 | -------------------------------------------------------------------------------- /diarist/baseline/translate_and_diarize.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | 5 | import fire 6 | import torch 7 | import torchaudio 8 | import whisper 9 | from speechbrain.pretrained import EncoderClassifier 10 | 11 | from diarist.baseline.utils import get_frame, get_list, dump_result 12 | from diarist.baseline.clustering import clustering 13 | 14 | 15 | def process_one_sample( 16 | in_wav, 17 | st_model, 18 | spk_model, 19 | beam_size=5, 20 | condition_on_previous_text=False, 21 | num_speakers=-1, 22 | max_num_speakers=6, 23 | min_dur=0.8, 24 | ): 25 | """process_one_sample""" 26 | 27 | torch.manual_seed(777) 28 | torch.cuda.manual_seed(777) 29 | 30 | # Speech translation 31 | decode_options = { 32 | "task": "translate", 33 | "beam_size": beam_size, 34 | "condition_on_previous_text": condition_on_previous_text, 35 | } 36 | st_result = st_model.transcribe(in_wav, **decode_options) 37 | 38 | # Speaker embedding extraction 39 | audio, sr = torchaudio.load(in_wav) 40 | assert sr == 16000 41 | assert audio.shape[0] == 1 42 | 43 | # extract speaker embedding with minimum duration of [min_dur] sec for each segment 44 | emb_list = [] 45 | for i, res in enumerate(st_result["segments"]): 46 | start_fr, end_fr = get_frame(res["start"], res["end"], audio.shape[1], min_dur) 47 | embedding = spk_model.encode_batch(audio[0, start_fr:end_fr]).reshape(-1) 48 | emb_list.append(embedding) 49 | stacked_embedding = torch.stack(emb_list) 50 | 51 | # clustering 52 | clust_result = clustering( 53 | stacked_embedding, num_speakers=num_speakers, max_num_speakers=max_num_speakers 54 | ) 55 | 56 | diar_result = [] 57 | for i, res in enumerate(st_result["segments"]): 58 | start = res["start"] 59 | end = res["end"] 60 | text = res["text"] 61 | text = " ".join(text.split()) # remove redundant spaces 62 | diar_result.append(f"guest_{clust_result[i]}\t{start}\t{end}\t{text}") 63 | return diar_result 64 | 65 | 66 | def translate_and_diarize_main( 67 | in_wav="", 68 | out_tsv="", 69 | in_dir="", 70 | out_dir="", 71 | st_model_size="small", 72 | beam_size=5, 73 | num_speakers=-1, 74 | max_num_speakers=6, 75 | min_dur=0.8, 76 | rank=0, 77 | world_size=1, 78 | ): 79 | """main""" 80 | torch.manual_seed(777) 81 | torch.cuda.manual_seed(777) 82 | 83 | # set model 84 | if torch.cuda.is_available(): 85 | st_model = whisper.load_model(st_model_size, device=f"cuda:{rank}") 86 | spk_model = EncoderClassifier.from_hparams( 87 | source="speechbrain/spkrec-ecapa-voxceleb", 88 | run_opts={"device": f"cuda:{rank}"}, 89 | ) 90 | else: 91 | st_model = whisper.load_model(st_model_size) 92 | spk_model = EncoderClassifier.from_hparams( 93 | source="speechbrain/spkrec-ecapa-voxceleb" 94 | ) 95 | 96 | # set input and output 97 | in_wav_list, out_tsv_list = get_list( 98 | in_wav, out_tsv, in_dir, out_dir, rank, world_size 99 | ) 100 | 101 | # process files 102 | for _in_wav, _out_tsv in zip(in_wav_list, out_tsv_list): 103 | if os.path.exists(_out_tsv): 104 | print(f"{_out_tsv} already exists. Skip.") 105 | continue 106 | 107 | print(f"Processing {_in_wav}") 108 | diar_result = process_one_sample( 109 | _in_wav, 110 | st_model, 111 | spk_model, 112 | beam_size=beam_size, 113 | num_speakers=num_speakers, 114 | max_num_speakers=max_num_speakers, 115 | min_dur=min_dur, 116 | ) 117 | 118 | print(f"Generate {_out_tsv}") 119 | dump_result(diar_result, _out_tsv) 120 | 121 | 122 | def main(): 123 | fire.Fire(translate_and_diarize_main) 124 | 125 | 126 | if __name__ == "__main__": 127 | fire.Fire(translate_and_diarize_main) 128 | -------------------------------------------------------------------------------- /diarist/scoring/eval.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import json 5 | import itertools 6 | from collections import defaultdict 7 | 8 | from sacrebleu.metrics import BLEU 9 | import numpy as np 10 | import fire 11 | 12 | 13 | def sagbleu(preds, ref_json, delimiter="\t"): 14 | preds = [" ".join(d.split(delimiter)[3:]) for d in preds] 15 | refs = [d["translation"] for d in ref_json] 16 | pred = " ".join(preds) # form a long sequence 17 | ref = " ".join(refs) 18 | 19 | bleu = BLEU(effective_order=True) 20 | session_bleu = bleu.corpus_score([pred], [[ref]]).score 21 | return [pred], [ref], session_bleu 22 | 23 | 24 | def satbleu(preds, ref_json, delimiter="\t"): 25 | spk2chunk_ref = defaultdict(list) 26 | spk2chunk_hyp = defaultdict(list) 27 | for seg in ref_json: 28 | seg_spk = seg["speaker"] 29 | spk2chunk_ref[seg_spk].append(seg["translation"]) 30 | for chunk in preds: 31 | spk = chunk.split(delimiter)[0] 32 | hyp = " ".join(chunk.split(delimiter)[3:]) 33 | spk2chunk_hyp[spk].append(hyp) 34 | bleu = BLEU() 35 | 36 | ref_spks = list(spk2chunk_ref.keys()) 37 | hyp_spks = list(spk2chunk_hyp.keys()) 38 | 39 | list_len = max(len(ref_spks), len(hyp_spks)) 40 | if list_len > 6: 41 | print( 42 | "Num of predicted speakers is larger than 6, evaluating this sample will be slow. " 43 | ) 44 | perms = list(itertools.permutations(range(list_len))) 45 | if len(ref_spks) < len(hyp_spks): 46 | ref_texts = [" ".join(spk_txt) for spk, spk_txt in spk2chunk_ref.items()] + [ 47 | "" 48 | ] * (len(hyp_spks) - len(ref_spks)) 49 | hyp_texts = [" ".join(spk_txt) for spk, spk_txt in spk2chunk_hyp.items()] 50 | 51 | elif len(ref_spks) > len(hyp_spks): 52 | ref_texts = [" ".join(spk_txt) for spk, spk_txt in spk2chunk_ref.items()] 53 | hyp_texts = [" ".join(spk_txt) for spk, spk_txt in spk2chunk_hyp.items()] + [ 54 | "" 55 | ] * (len(ref_spks) - len(hyp_spks)) 56 | 57 | elif len(ref_spks) == len(hyp_spks): 58 | ref_texts = [" ".join(spk_txt) for spk, spk_txt in spk2chunk_ref.items()] 59 | hyp_texts = [" ".join(spk_txt) for spk, spk_txt in spk2chunk_hyp.items()] 60 | 61 | max_perm = perms[0] 62 | max_score = bleu.corpus_score([hyp_texts[j] for j in max_perm], [ref_texts]).score 63 | 64 | for i, perm in enumerate(perms[1:]): 65 | score = bleu.corpus_score([hyp_texts[j] for j in perm], [ref_texts]).score 66 | if score > max_score: 67 | max_score = score 68 | max_perm = perm 69 | return [hyp_texts[j] for j in max_perm], ref_texts, max_score 70 | 71 | 72 | def evaluate(ref_dir, hyp_dir): 73 | full_path_list = [ 74 | os.path.join(hyp_dir, f) for f in os.listdir(hyp_dir) if f.endswith(".tsv") 75 | ] 76 | print(f"Found {len(full_path_list)} files in {hyp_dir}") 77 | 78 | for eval_method in ["SAgBLEU", "SAtBLEU"]: 79 | dir_scores = { 80 | "details": {}, 81 | } 82 | 83 | total_hyps, total_refs = [], [] 84 | for dname in full_path_list: 85 | # assume parallel dir structure, search for ref file in `ref_dir` 86 | 87 | just_name = os.path.splitext(os.path.basename(dname))[0] 88 | ref_json_path = os.path.join(ref_dir, f"{just_name}.json") 89 | 90 | with open(ref_json_path, "r") as f: 91 | ref_json = json.load(f) 92 | 93 | with open(dname, "r") as f: 94 | preds = f.read().split("\n") 95 | 96 | preds = [p for p in preds if p not in [" ", ""]] 97 | if eval_method == "SAtBLEU": 98 | hyp, ref, session_bleu = satbleu(preds, ref_json) 99 | elif eval_method == "SAgBLEU": 100 | hyp, ref, session_bleu = sagbleu(preds, ref_json) 101 | else: 102 | raise NotImplementedError 103 | total_hyps.extend(hyp) 104 | total_refs.extend(ref) 105 | 106 | dir_scores["details"].update({dname: {"session_bleu": session_bleu,}}) 107 | avg_session_bleu = np.mean( 108 | [d["session_bleu"] for d in dir_scores["details"].values()] 109 | ) 110 | dir_scores["avg_session_bleu"] = avg_session_bleu 111 | 112 | bleu = BLEU() 113 | lang_level_corpus_bleu = bleu.corpus_score(total_hyps, [total_refs]).score 114 | dir_scores["corpus_bleu"] = lang_level_corpus_bleu 115 | 116 | print("{}: {:.2f}".format(eval_method, dir_scores["corpus_bleu"])) 117 | 118 | 119 | def main(): 120 | fire.Fire(evaluate) 121 | 122 | 123 | if __name__ == "__main__": 124 | fire.Fire(evaluate) 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DiariST 2 | This repository maintains the data and code used for the paper "DiariST: Streaming Speech Translation with Speaker Diarization". 3 | 4 | [Read the paper](https://arxiv.org/abs/2309.08007) 5 | ## Overview 6 | End-to-end speech translation (ST) for conversation recordings involves several underexplored challenges such as speaker diarization (SD) without accurate word time stamps and handling of overlapping speech in a streaming fashion. Due to the absence of evaluation benchmarks in this area, we develop a new evaluation dataset, **DiariST-AliMeeting**, by translating the reference Chinese transcriptions of the [AliMeeting](https://www.openslr.org/119/) into English. We also propose new metrics, called **Speaker-Agnostic BLEU (SAgBLEU)** and **Speaker-Attributed BLEU (SAtBLEU)**, to measure the ST quality while taking SD accuracy into account. In our paper [DiariST: Streaming Speech Translation with Speaker Diarization](https://arxiv.org/abs/2309.08007), we further propose the first streaming ST and SD system, named **DiariST**, by integrating [token-level serialized output training](https://arxiv.org/abs/2202.00842) and [t-vector](https://arxiv.org/abs/2203.16685) into [a neural transducer-based streaming ST system](https://arxiv.org/abs/2204.05352). To facilitate the research in this new direction, we release the evaluation data, the offline baseline systems, and the evaluation code, used in the paper. 7 | 8 | 9 | ## Prerequisites 10 | - Linux 11 | - python 3.9 12 | 13 | ## Installation 14 | ```sh 15 | pip install git+https://github.com/openai/whisper.git 16 | git clone https://github.com/Mu-Y/DiariST.git 17 | cd DiariST 18 | pip install -e . 19 | ``` 20 | 21 | ## How to generate the Diarist-AliMeeting data 22 | ```sh 23 | $ ./run_prepare_data.sh 24 | ``` 25 | The audio files and corresponding reference json files are generated under ./data/ directory as following structures. 26 | ``` 27 | data 28 | └── DiariST-AliMeeting 29 | └── [IHM-CAT, IHM-MIX, SDM] 30 | └── [dev, test] 31 | ├── Rxxx_Myyy_start_end.wav 32 | ├── Rxxx_Myyy_start_end.json 33 | ├── ... 34 | ``` 35 | 36 | ## How to run the baseline system 37 | - Translation --> Diarization 38 | - Option 1: Run "translation --> diarization" baseline for one audio sample. 39 | ```sh 40 | $ diarist_baseline_td --in_wav data/DiariST-AliMeeting/IHM-CAT/test/R8002_M8002-0-249.06.wav --out_tsv result/DiariST-AliMeeting/IHM-CAT/test/R8002_M8002-0-249.06.tsv 41 | ``` 42 | 43 | - Option 2: Run "translation --> diarization" baseline for all audio samples under data/DiariST-AliMeeting/IHM-CAT/test/. (CAUTION: it will take long time because this script applies the baseline for each audio one by one without any parallelization.) 44 | ```sh 45 | $ diarist_baseline_td --in_dir data/DiariST-AliMeeting/IHM-CAT/test/ --out_dir result/DiariST-AliMeeting/IHM-CAT/test/ 46 | ``` 47 | 48 | - Diarization --> Translation 49 | - Please use a command "diarist_baseline_dt" instead of "diarist_baseline_td" 50 | 51 | ## How to evaluate the result 52 | Assuming that reference translations are stored under "./data/DiariST-AliMeeting/IHM-CAT/test/" and the diarized speech translation results are stored under "./result/DiariST-AliMeeting/IHM-CAT/test/" in TSV format, you can compute SAgBLEU and SAtBLEU using the following command. 53 | ```sh 54 | $ diarist_eval \ 55 | --ref_dir ./data/DiariST-AliMeeting/IHM-CAT/test/ \ 56 | --hyp_dir ./result/DiariST-AliMeeting/IHM-CAT/test/ 57 | ``` 58 | 59 | It will compute SAgBLEU and SAtBLEU score as follows. (Note: Our results in the paper were obtained using the Tesla V100 with 16GB of memory. The results may vary depending on the computational environment.) 60 | ```sh 61 | Found 195 files in result/DiariST-AliMeeting/IHM-CAT/test/ 62 | SAgBLEU: 18.45 63 | SAtBLEU: 16.81 64 | ``` 65 | 66 | Note that SAgBLEU and SAtBLEU are uttearnce-order sensitive, but not time-stamp sensitive. If your speech translation system does not generate precise timestamps, you can simply set dummy timestamps in the TSV file. 67 | 68 | ## License 69 | | | License | 70 | | ------------- |:-------------:| 71 | | DiariST-AliMeeting | [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) | 72 | | Anything else | [MIT Licence](LICENSE.txt) | 73 | 74 | ## Citation 75 | Please cite the first paper (yang2023diarist) when you use the code in this repository. If you use the DiariST-AliMeeting test set under data/ directory, please also cite the two papers for AliMeeting corpus (Yu2022M2Met, Yu2022Summary) in addition to the first paper (yang2023diarist). 76 | ``` 77 | @article{yang2023diarist, 78 | title={{DiariST}: Streaming Speech Translation with Speaker Diarization}, 79 | author={Yang, Mu and Kanda, Naoyuki and Wang, Xiaofei and Chen, Junkun and Wang, Peidong and Xue, Jian and Li, Jinyu and Yoshioka, Takuya}, 80 | journal={arXiv preprint arXiv:2309.08007}, 81 | year={2023} 82 | } 83 | 84 | @inproceedings{Yu2022M2MeT, 85 | title={M2{M}e{T}: The {ICASSP} 2022 Multi-Channel Multi-Party Meeting Transcription Challenge}, 86 | author={Yu, Fan and Zhang, Shiliang and Fu, Yihui and Xie, Lei and Zheng, Siqi and Du, Zhihao and Huang, Weilong and Guo, Pengcheng and Yan, Zhijie and Ma, Bin and Xu, Xin and Bu, Hui}, 87 | booktitle={Proc. ICASSP}, 88 | pages={6167--6171}, 89 | year={2022}, 90 | organization={IEEE} 91 | } 92 | 93 | @inproceedings{Yu2022Summary, 94 | title={Summary On The {ICASSP} 2022 Multi-Channel Multi-Party Meeting Transcription Grand Challenge}, 95 | author={Yu, Fan and Zhang, Shiliang and Guo, Pengcheng and Fu, Yihui and Du, Zhihao and Zheng, Siqi and Huang, Weilong and Xie, Lei and Tan, Zheng-Hua and Wang, DeLiang and Qian, Yanmin and Lee, Kong Aik and Yan, Zhijie and Ma, Bin and Xu, Xin and Bu, Hui}, 96 | booktitle={Proc. ICASSP}, 97 | pages={9156--9160}, 98 | year={2022}, 99 | organization={IEEE} 100 | } 101 | ``` 102 | -------------------------------------------------------------------------------- /diarist/baseline/diarize_and_translate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | 5 | import fire 6 | import torch 7 | import torchaudio 8 | import whisper 9 | from speechbrain.pretrained import EncoderClassifier 10 | from speechbrain.pretrained import VAD 11 | 12 | 13 | from diarist.baseline.utils import get_frame, get_list, dump_result 14 | from diarist.baseline.clustering import clustering 15 | 16 | 17 | def process_one_sample( 18 | in_wav, 19 | st_model, 20 | spk_model, 21 | vad_model=None, 22 | beam_size=5, 23 | condition_on_previous_text=False, 24 | num_speakers=-1, 25 | max_num_speakers=6, 26 | window_size=1.2, 27 | window_shift=0.6, 28 | ): 29 | """process_one_sample""" 30 | 31 | torch.manual_seed(777) 32 | torch.cuda.manual_seed(777) 33 | 34 | # 35 | # Speaker embedding extraction 36 | # 37 | audio, sr = torchaudio.load(in_wav) 38 | assert sr == 16000 39 | assert audio.shape[0] == 1 40 | 41 | # VAD 42 | if vad_model is not None: 43 | boundaries = vad_model.get_speech_segments(in_wav).tolist() 44 | else: 45 | boundaries = [[0.0, audio.shape[1] / sr]] 46 | 47 | # extract speaker embeddings with sliding window 48 | emb_list = [] 49 | for boundary_start, boundary_end in boundaries: 50 | boundary_dur = boundary_end - boundary_start 51 | num_shift = int(boundary_dur / window_shift) 52 | for i in range(num_shift): 53 | start_sec = boundary_start + window_shift * i 54 | start_fr, end_fr = get_frame( 55 | start_sec, start_sec + window_size, audio.shape[1], window_size 56 | ) 57 | embedding = spk_model.encode_batch(audio[0, start_fr:end_fr]).reshape(-1) 58 | emb_list.append(embedding) 59 | stacked_embedding = torch.stack(emb_list) 60 | 61 | # clustering 62 | clust_result = clustering( 63 | stacked_embedding, num_speakers=num_speakers, max_num_speakers=max_num_speakers 64 | ) 65 | 66 | # aggregate segments for the same speaker 67 | segment_result = [] 68 | embedding_index = 0 69 | for boundary_start, boundary_end in boundaries: 70 | seg_start = boundary_start 71 | prev_time = boundary_start 72 | prev_spk = -1 73 | boundary_dur = boundary_end - boundary_start 74 | num_shift = int(boundary_dur / window_shift) 75 | for i in range(num_shift): 76 | # for i in range(len(emb_list)): 77 | cur_time = min( 78 | boundary_start + window_size * 0.5 + window_shift * (i + 0.5), 79 | boundary_end, 80 | ) 81 | cur_spk = clust_result[embedding_index].item() 82 | embedding_index += ( 83 | 1 # this must be align with the embedding extraction loop 84 | ) 85 | 86 | if cur_spk != prev_spk and prev_spk != -1: 87 | segment_result.append([seg_start, prev_time, prev_spk]) 88 | seg_start = cur_time 89 | prev_time = cur_time 90 | prev_spk = cur_spk 91 | if seg_start != prev_time: 92 | segment_result.append([seg_start, prev_time, prev_spk]) 93 | 94 | # 95 | # apply speech translation 96 | # 97 | diar_result = [] 98 | for start, end, spk in segment_result: 99 | decode_options = { 100 | "task": "translate", 101 | "beam_size": beam_size, 102 | "condition_on_previous_text": condition_on_previous_text, 103 | } 104 | st_result = st_model.transcribe( 105 | audio[0, int(start * sr) : int(end * sr)], **decode_options 106 | ) 107 | text = st_result["text"] 108 | text = " ".join(text.split()) # remove redundant spaces 109 | if text != "": 110 | diar_result.append(f"guest_{spk}\t{start}\t{end}\t{text}") 111 | return diar_result 112 | 113 | 114 | def diarize_and_translate_main( 115 | in_wav="", 116 | out_tsv="", 117 | in_dir="", 118 | out_dir="", 119 | st_model_size="small", 120 | beam_size=5, 121 | num_speakers=-1, 122 | max_num_speakers=6, 123 | window_size=1.2, 124 | window_shift=0.6, 125 | apply_VAD=True, 126 | rank=0, 127 | world_size=1, 128 | ): 129 | """main""" 130 | 131 | torch.manual_seed(777) 132 | torch.cuda.manual_seed(777) 133 | 134 | # set models 135 | if torch.cuda.is_available(): 136 | st_model = whisper.load_model(st_model_size, device=f"cuda:{rank}") 137 | spk_model = EncoderClassifier.from_hparams( 138 | source="speechbrain/spkrec-ecapa-voxceleb", 139 | run_opts={"device": f"cuda:{rank}"}, 140 | ) 141 | else: 142 | st_model = whisper.load_model(st_model_size) 143 | spk_model = EncoderClassifier.from_hparams( 144 | source="speechbrain/spkrec-ecapa-voxceleb" 145 | ) 146 | vad_model = None 147 | if apply_VAD: 148 | vad_model = VAD.from_hparams(source="speechbrain/vad-crdnn-libriparty") 149 | 150 | # set input and output 151 | in_wav_list, out_tsv_list = get_list( 152 | in_wav, out_tsv, in_dir, out_dir, rank, world_size 153 | ) 154 | 155 | # process files 156 | for _in_wav, _out_tsv in zip(in_wav_list, out_tsv_list): 157 | if os.path.exists(_out_tsv): 158 | print(f"{_out_tsv} already exists. Skip.") 159 | continue 160 | 161 | print(f"Processing {_in_wav}") 162 | diar_result = process_one_sample( 163 | _in_wav, 164 | st_model, 165 | spk_model, 166 | vad_model=vad_model, 167 | beam_size=beam_size, 168 | num_speakers=num_speakers, 169 | max_num_speakers=max_num_speakers, 170 | window_size=window_size, 171 | window_shift=window_shift, 172 | ) 173 | 174 | print(f"Generate {_out_tsv}") 175 | dump_result(diar_result, _out_tsv) 176 | 177 | 178 | def main(): 179 | fire.Fire(diarize_and_translate_main) 180 | 181 | 182 | if __name__ == "__main__": 183 | fire.Fire(diarize_and_translate_main) 184 | -------------------------------------------------------------------------------- /diarist/data/generate_diarist_alimeeting.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import os 5 | import json 6 | import glob 7 | import re 8 | 9 | import soundfile as sf 10 | import numpy as np 11 | 12 | ROOT_DIR = "data/DiariST-AliMeeting/" 13 | SUBSETS = ["dev", "test"] 14 | 15 | 16 | def fix_timing(data, start): 17 | """fix_timing""" 18 | for elem in data: 19 | elem["start"] -= start 20 | elem["end"] -= start 21 | return data 22 | 23 | 24 | def gen_sdm(in_json, ali_meeting_dir, subset): 25 | """generate single distant microphone data""" 26 | with open(in_json) as fp: 27 | all_data = json.load(fp) 28 | 29 | prev_session = "" 30 | for one_session in all_data: 31 | session = one_session["session"] 32 | data = one_session["data"] 33 | start = one_session["start"] 34 | end = one_session["end"] 35 | 36 | if prev_session != session: 37 | if subset == "dev": 38 | audio_files = glob.glob( 39 | f"{ali_meeting_dir}/Eval_Ali/Eval_Ali_far/audio_dir/{session}_*.wav" 40 | ) 41 | elif subset == "test": 42 | audio_files = glob.glob( 43 | f"{ali_meeting_dir}/Test_Ali/Test_Ali_far/audio_dir/{session}_*.wav" 44 | ) 45 | assert len(audio_files) == 1 46 | audio_file = audio_files[0] 47 | 48 | audio, sr = sf.read(audio_file) 49 | prev_session = session 50 | 51 | out_wav = f"{ROOT_DIR}/SDM/{subset}/{session}-{start}-{end}.wav" 52 | out_json = f"{ROOT_DIR}/SDM/{subset}/{session}-{start}-{end}.json" 53 | mkdir = os.path.dirname(out_wav) 54 | if not os.path.exists(mkdir): 55 | os.makedirs(mkdir, exist_ok=True) 56 | 57 | start_fr = int(start * sr) 58 | end_fr = int(end * sr) 59 | 60 | if os.path.exists(out_wav): 61 | print(f"{out_wav} exists, skip") 62 | else: 63 | sf.write(out_wav, audio[start_fr:end_fr, 0], sr) 64 | 65 | time_fixed_data = fix_timing(data, start) 66 | with open(out_json, "w", encoding="utf-8") as fp: 67 | json.dump(time_fixed_data, fp, indent=4, ensure_ascii=False) 68 | 69 | 70 | def gen_ihm_mix(in_json, ali_meeting_dir, subset): 71 | """generate mixture of independent head microphones""" 72 | with open(in_json) as fp: 73 | all_data = json.load(fp) 74 | 75 | prev_session = "" 76 | max_dur_idx = 0 77 | sr = 16000 78 | for one_session in all_data: 79 | session = one_session["session"] 80 | data = one_session["data"] 81 | start = one_session["start"] 82 | end = one_session["end"] 83 | 84 | # create IHM-MIX 85 | if prev_session != session: 86 | if subset == "dev": 87 | audio_files = glob.glob( 88 | f"{ali_meeting_dir}/Eval_Ali/Eval_Ali_near/audio_dir/{session}_*.wav" 89 | ) 90 | elif subset == "test": 91 | audio_files = glob.glob( 92 | f"{ali_meeting_dir}/Test_Ali/Test_Ali_near/audio_dir/{session}_*.wav" 93 | ) 94 | assert len(audio_files) <= 4 95 | 96 | audios = [] 97 | max_dur = 0 98 | max_dur_idx = 0 99 | for i, audio_file in enumerate(audio_files): 100 | audio, sr = sf.read(audio_file) 101 | audios.append(audio) 102 | if len(audio) > max_dur: 103 | max_dur = len(audio) 104 | max_dur_idx = i 105 | for i in range(len(audios)): 106 | if i != max_dur_idx: 107 | audios[max_dur_idx][: len(audios[i])] += audios[i] 108 | prev_session = session 109 | 110 | out_wav = f"{ROOT_DIR}/IHM-MIX/{subset}/{session}-{start}-{end}.wav" 111 | out_json = f"{ROOT_DIR}/IHM-MIX/{subset}/{session}-{start}-{end}.json" 112 | mkdir = os.path.dirname(out_wav) 113 | if not os.path.exists(mkdir): 114 | os.makedirs(mkdir, exist_ok=True) 115 | 116 | start_fr = int(start * sr) 117 | end_fr = int(end * sr) 118 | 119 | if os.path.exists(out_wav): 120 | print(f"{out_wav} exists, skip") 121 | else: 122 | sf.write(out_wav, audios[max_dur_idx][start_fr:end_fr], sr) 123 | 124 | time_fixed_data = fix_timing(data, start) 125 | with open(out_json, "w", encoding="utf-8") as fp: 126 | json.dump(time_fixed_data, fp, indent=4, ensure_ascii=False) 127 | 128 | 129 | def gen_ihm_cat(in_json, ali_meeting_dir, subset): 130 | """generate concat of independent head microphones""" 131 | with open(in_json) as fp: 132 | all_data = json.load(fp) 133 | 134 | prev_session = "" 135 | for one_session in all_data: 136 | session = one_session["session"] 137 | data = one_session["data"] 138 | session_start = one_session["start"] 139 | session_end = one_session["end"] 140 | 141 | if prev_session != session: 142 | if subset == "dev": 143 | audio_files = glob.glob( 144 | f"{ali_meeting_dir}/Eval_Ali/Eval_Ali_near/audio_dir/{session}_*.wav" 145 | ) 146 | elif subset == "test": 147 | audio_files = glob.glob( 148 | f"{ali_meeting_dir}/Test_Ali/Test_Ali_near/audio_dir/{session}_*.wav" 149 | ) 150 | assert len(audio_files) <= 4 151 | 152 | sr = 16000 153 | audios = {} 154 | for i, audio_file in enumerate(audio_files): 155 | basename = os.path.basename(audio_file) 156 | match = re.search(r"\w+_(N_SPK\d+).wav", basename) 157 | if not match: 158 | raise RuntimeError(f"Failed to parse {basename}") 159 | spk = match.group(1) 160 | audio, sr = sf.read(audio_file) 161 | assert sr == 16000 162 | audios[spk] = audio 163 | 164 | audio_to_concate = [] 165 | for i, elem in enumerate(data): 166 | spk = elem["speaker"] 167 | start_fr = int(elem["start"] * sr) 168 | end_fr = int(elem["end"] * sr) 169 | audio_to_concate.append(audios[spk][start_fr:end_fr]) 170 | audio = np.concatenate(audio_to_concate) 171 | 172 | new_data = [] 173 | new_start = 0 174 | new_end = 0 175 | for elem in data: 176 | start = elem["start"] 177 | end = elem["end"] 178 | new_start = new_end 179 | new_end = new_start + end - start 180 | new_data.append( 181 | { 182 | "speaker": elem["speaker"], 183 | "start": new_start, 184 | "end": new_end, 185 | "text": elem["text"], 186 | "translation": elem["translation"], 187 | } 188 | ) 189 | 190 | out_wav = ( 191 | f"{ROOT_DIR}/IHM-CAT/{subset}/{session}-{session_start}-{session_end}.wav" 192 | ) 193 | out_json = ( 194 | f"{ROOT_DIR}/IHM-CAT/{subset}/{session}-{session_start}-{session_end}.json" 195 | ) 196 | mkdir = os.path.dirname(out_wav) 197 | if not os.path.exists(mkdir): 198 | os.makedirs(mkdir, exist_ok=True) 199 | 200 | if os.path.exists(out_wav): 201 | print(f"{out_wav} exists, skip") 202 | else: 203 | sf.write(out_wav, audio, sr) 204 | 205 | with open(out_json, "w", encoding="utf-8") as fp: 206 | json.dump(new_data, fp, indent=4, ensure_ascii=False) 207 | 208 | 209 | if __name__ == "__main__": 210 | if len(sys.argv) != 2: 211 | print(f"Usage: {sys.argv[0]} ") 212 | sys.exit(1) 213 | 214 | ALI_MEETING_DIR = sys.argv[1] 215 | 216 | for subset in SUBSETS: 217 | json_file = f"{ROOT_DIR}/{subset}.json" 218 | 219 | gen_sdm(json_file, ALI_MEETING_DIR, subset) 220 | gen_ihm_mix(json_file, ALI_MEETING_DIR, subset) 221 | gen_ihm_cat(json_file, ALI_MEETING_DIR, subset) 222 | -------------------------------------------------------------------------------- /diarist/data/dev.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "session": "R8008_M8013", 4 | "start": 0, 5 | "end": 180.18 6 | }, 7 | { 8 | "session": "R8008_M8013", 9 | "start": 180.06, 10 | "end": 363.06 11 | }, 12 | { 13 | "session": "R8008_M8013", 14 | "start": 361.79, 15 | "end": 542.99 16 | }, 17 | { 18 | "session": "R8008_M8013", 19 | "start": 542.21, 20 | "end": 722.45 21 | }, 22 | { 23 | "session": "R8008_M8013", 24 | "start": 722.18, 25 | "end": 910.58 26 | }, 27 | { 28 | "session": "R8008_M8013", 29 | "start": 909.01, 30 | "end": 1090.47 31 | }, 32 | { 33 | "session": "R8008_M8013", 34 | "start": 1090.2, 35 | "end": 1270.62 36 | }, 37 | { 38 | "session": "R8008_M8013", 39 | "start": 1269.57, 40 | "end": 1453.62 41 | }, 42 | { 43 | "session": "R8008_M8013", 44 | "start": 1452.61, 45 | "end": 1633.47 46 | }, 47 | { 48 | "session": "R8008_M8013", 49 | "start": 1632.22, 50 | "end": 1812.51 51 | }, 52 | { 53 | "session": "R8008_M8013", 54 | "start": 1811.38, 55 | "end": 1991.61 56 | }, 57 | { 58 | "session": "R8008_M8013", 59 | "start": 1991.4, 60 | "end": 2235.69 61 | }, 62 | { 63 | "session": "R8007_M8010", 64 | "start": 0, 65 | "end": 206.38 66 | }, 67 | { 68 | "session": "R8007_M8010", 69 | "start": 206.35, 70 | "end": 450.01 71 | }, 72 | { 73 | "session": "R8007_M8010", 74 | "start": 449.16, 75 | "end": 675.36 76 | }, 77 | { 78 | "session": "R8007_M8010", 79 | "start": 674.85, 80 | "end": 891.02 81 | }, 82 | { 83 | "session": "R8007_M8010", 84 | "start": 890.17, 85 | "end": 1093.48 86 | }, 87 | { 88 | "session": "R8007_M8010", 89 | "start": 1093.41, 90 | "end": 1312.18 91 | }, 92 | { 93 | "session": "R8007_M8010", 94 | "start": 1311.82, 95 | "end": 1501.98 96 | }, 97 | { 98 | "session": "R8007_M8010", 99 | "start": 1501.65, 100 | "end": 1854.1 101 | }, 102 | { 103 | "session": "R8009_M8020", 104 | "start": 0, 105 | "end": 181.44 106 | }, 107 | { 108 | "session": "R8009_M8020", 109 | "start": 180.29, 110 | "end": 363.03 111 | }, 112 | { 113 | "session": "R8009_M8020", 114 | "start": 362.27, 115 | "end": 544.02 116 | }, 117 | { 118 | "session": "R8009_M8020", 119 | "start": 543.65, 120 | "end": 726.57 121 | }, 122 | { 123 | "session": "R8009_M8020", 124 | "start": 726.57, 125 | "end": 907.6 126 | }, 127 | { 128 | "session": "R8009_M8020", 129 | "start": 907.6, 130 | "end": 1091.85 131 | }, 132 | { 133 | "session": "R8009_M8020", 134 | "start": 1091.54, 135 | "end": 1272.65 136 | }, 137 | { 138 | "session": "R8009_M8020", 139 | "start": 1272.65, 140 | "end": 1453.02 141 | }, 142 | { 143 | "session": "R8009_M8020", 144 | "start": 1453.02, 145 | "end": 1634.05 146 | }, 147 | { 148 | "session": "R8009_M8020", 149 | "start": 1634.05, 150 | "end": 1907.8 151 | }, 152 | { 153 | "session": "R8003_M8001", 154 | "start": 0, 155 | "end": 188.81 156 | }, 157 | { 158 | "session": "R8003_M8001", 159 | "start": 188.35, 160 | "end": 387.75 161 | }, 162 | { 163 | "session": "R8003_M8001", 164 | "start": 387.46, 165 | "end": 577.33 166 | }, 167 | { 168 | "session": "R8003_M8001", 169 | "start": 575.63, 170 | "end": 767.65 171 | }, 172 | { 173 | "session": "R8003_M8001", 174 | "start": 766.54, 175 | "end": 951.0 176 | }, 177 | { 178 | "session": "R8003_M8001", 179 | "start": 949.32, 180 | "end": 1149.38 181 | }, 182 | { 183 | "session": "R8003_M8001", 184 | "start": 1148.8, 185 | "end": 1331.73 186 | }, 187 | { 188 | "session": "R8003_M8001", 189 | "start": 1331.52, 190 | "end": 1519.89 191 | }, 192 | { 193 | "session": "R8003_M8001", 194 | "start": 1518.96, 195 | "end": 1705.12 196 | }, 197 | { 198 | "session": "R8003_M8001", 199 | "start": 1704.37, 200 | "end": 1885.4 201 | }, 202 | { 203 | "session": "R8003_M8001", 204 | "start": 1885.05, 205 | "end": 2067.9 206 | }, 207 | { 208 | "session": "R8009_M8019", 209 | "start": 0, 210 | "end": 184.02 211 | }, 212 | { 213 | "session": "R8009_M8019", 214 | "start": 183.52, 215 | "end": 363.63 216 | }, 217 | { 218 | "session": "R8009_M8019", 219 | "start": 363.29, 220 | "end": 544.4 221 | }, 222 | { 223 | "session": "R8009_M8019", 224 | "start": 544.4, 225 | "end": 728.97 226 | }, 227 | { 228 | "session": "R8009_M8019", 229 | "start": 728.6, 230 | "end": 909.16 231 | }, 232 | { 233 | "session": "R8009_M8019", 234 | "start": 909.16, 235 | "end": 1090.61 236 | }, 237 | { 238 | "session": "R8009_M8019", 239 | "start": 1090.61, 240 | "end": 1273.74 241 | }, 242 | { 243 | "session": "R8009_M8019", 244 | "start": 1272.98, 245 | "end": 1455.63 246 | }, 247 | { 248 | "session": "R8009_M8019", 249 | "start": 1455.2, 250 | "end": 1640.89 251 | }, 252 | { 253 | "session": "R8009_M8019", 254 | "start": 1640.89, 255 | "end": 1964.86 256 | }, 257 | { 258 | "session": "R8001_M8004", 259 | "start": 0, 260 | "end": 186.34 261 | }, 262 | { 263 | "session": "R8001_M8004", 264 | "start": 185.88, 265 | "end": 451.89 266 | }, 267 | { 268 | "session": "R8001_M8004", 269 | "start": 451.16, 270 | "end": 667.88 271 | }, 272 | { 273 | "session": "R8001_M8004", 274 | "start": 667.77, 275 | "end": 848.98 276 | }, 277 | { 278 | "session": "R8001_M8004", 279 | "start": 848.53, 280 | "end": 1031.41 281 | }, 282 | { 283 | "session": "R8001_M8004", 284 | "start": 1030.88, 285 | "end": 1297.72 286 | }, 287 | { 288 | "session": "R8001_M8004", 289 | "start": 1297.72, 290 | "end": 1551.14 291 | }, 292 | { 293 | "session": "R8007_M8011", 294 | "start": 0, 295 | "end": 192.15 296 | }, 297 | { 298 | "session": "R8007_M8011", 299 | "start": 192.05, 300 | "end": 382.74 301 | }, 302 | { 303 | "session": "R8007_M8011", 304 | "start": 382.63, 305 | "end": 574.61 306 | }, 307 | { 308 | "session": "R8007_M8011", 309 | "start": 574.33, 310 | "end": 764.97 311 | }, 312 | { 313 | "session": "R8007_M8011", 314 | "start": 764.02, 315 | "end": 972.93 316 | }, 317 | { 318 | "session": "R8007_M8011", 319 | "start": 972.91, 320 | "end": 1155.34 321 | }, 322 | { 323 | "session": "R8007_M8011", 324 | "start": 1154.38, 325 | "end": 1352.83 326 | }, 327 | { 328 | "session": "R8007_M8011", 329 | "start": 1351.64, 330 | "end": 1545.73 331 | }, 332 | { 333 | "session": "R8007_M8011", 334 | "start": 1545.73, 335 | "end": 1859.18 336 | }, 337 | { 338 | "session": "R8009_M8018", 339 | "start": 0, 340 | "end": 187.94 341 | }, 342 | { 343 | "session": "R8009_M8018", 344 | "start": 187.94, 345 | "end": 370.89 346 | }, 347 | { 348 | "session": "R8009_M8018", 349 | "start": 370.89, 350 | "end": 554.01 351 | }, 352 | { 353 | "session": "R8009_M8018", 354 | "start": 553.62, 355 | "end": 735.08 356 | }, 357 | { 358 | "session": "R8009_M8018", 359 | "start": 734.72, 360 | "end": 915.15 361 | }, 362 | { 363 | "session": "R8009_M8018", 364 | "start": 915.15, 365 | "end": 1095.52 366 | }, 367 | { 368 | "session": "R8009_M8018", 369 | "start": 1094.95, 370 | "end": 1281.76 371 | }, 372 | { 373 | "session": "R8009_M8018", 374 | "start": 1281.27, 375 | "end": 1464.02 376 | }, 377 | { 378 | "session": "R8009_M8018", 379 | "start": 1463.71, 380 | "end": 1651.68 381 | } 382 | ] 383 | -------------------------------------------------------------------------------- /diarist/data/test.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "session": "R8008_M8016", 4 | "start": 0, 5 | "end": 187.51 6 | }, 7 | { 8 | "session": "R8008_M8016", 9 | "start": 186.75, 10 | "end": 367.26 11 | }, 12 | { 13 | "session": "R8008_M8016", 14 | "start": 366.77, 15 | "end": 558.21 16 | }, 17 | { 18 | "session": "R8008_M8016", 19 | "start": 558.21, 20 | "end": 751.17 21 | }, 22 | { 23 | "session": "R8008_M8016", 24 | "start": 750.47, 25 | "end": 931.43 26 | }, 27 | { 28 | "session": "R8008_M8016", 29 | "start": 931.06, 30 | "end": 1126.55 31 | }, 32 | { 33 | "session": "R8008_M8016", 34 | "start": 1126.03, 35 | "end": 1309.11 36 | }, 37 | { 38 | "session": "R8008_M8016", 39 | "start": 1309.11, 40 | "end": 1493.38 41 | }, 42 | { 43 | "session": "R8008_M8016", 44 | "start": 1492.07, 45 | "end": 1836.62 46 | }, 47 | { 48 | "session": "R8008_M8015", 49 | "start": 0, 50 | "end": 188.66 51 | }, 52 | { 53 | "session": "R8008_M8015", 54 | "start": 188.66, 55 | "end": 374.66 56 | }, 57 | { 58 | "session": "R8008_M8015", 59 | "start": 374.66, 60 | "end": 557.29 61 | }, 62 | { 63 | "session": "R8008_M8015", 64 | "start": 557.01, 65 | "end": 744.38 66 | }, 67 | { 68 | "session": "R8008_M8015", 69 | "start": 744.38, 70 | "end": 933.37 71 | }, 72 | { 73 | "session": "R8008_M8015", 74 | "start": 930.65, 75 | "end": 1120.35 76 | }, 77 | { 78 | "session": "R8008_M8015", 79 | "start": 1120.35, 80 | "end": 1311.38 81 | }, 82 | { 83 | "session": "R8008_M8015", 84 | "start": 1310.54, 85 | "end": 1497.71 86 | }, 87 | { 88 | "session": "R8008_M8015", 89 | "start": 1497.59, 90 | "end": 1687.27 91 | }, 92 | { 93 | "session": "R8008_M8015", 94 | "start": 1687.27, 95 | "end": 1896.06 96 | }, 97 | { 98 | "session": "R8009_M8023", 99 | "start": 0, 100 | "end": 180.39 101 | }, 102 | { 103 | "session": "R8009_M8023", 104 | "start": 180.05, 105 | "end": 361.37 106 | }, 107 | { 108 | "session": "R8009_M8023", 109 | "start": 360.83, 110 | "end": 543.22 111 | }, 112 | { 113 | "session": "R8009_M8023", 114 | "start": 543.22, 115 | "end": 723.38 116 | }, 117 | { 118 | "session": "R8009_M8023", 119 | "start": 723.38, 120 | "end": 904.73 121 | }, 122 | { 123 | "session": "R8009_M8023", 124 | "start": 904.73, 125 | "end": 1089.03 126 | }, 127 | { 128 | "session": "R8009_M8023", 129 | "start": 1089.03, 130 | "end": 1270.12 131 | }, 132 | { 133 | "session": "R8009_M8023", 134 | "start": 1270.12, 135 | "end": 1451.43 136 | }, 137 | { 138 | "session": "R8009_M8023", 139 | "start": 1450.79, 140 | "end": 1631.09 141 | }, 142 | { 143 | "session": "R8009_M8023", 144 | "start": 1631.09, 145 | "end": 1961.25 146 | }, 147 | { 148 | "session": "R8004_M8005", 149 | "start": 0, 150 | "end": 216.53 151 | }, 152 | { 153 | "session": "R8004_M8005", 154 | "start": 215.85, 155 | "end": 403.5 156 | }, 157 | { 158 | "session": "R8004_M8005", 159 | "start": 403.29, 160 | "end": 624.26 161 | }, 162 | { 163 | "session": "R8004_M8005", 164 | "start": 623.75, 165 | "end": 864.9 166 | }, 167 | { 168 | "session": "R8004_M8005", 169 | "start": 864.9, 170 | "end": 1064.07 171 | }, 172 | { 173 | "session": "R8004_M8005", 174 | "start": 1064.04, 175 | "end": 1250.31 176 | }, 177 | { 178 | "session": "R8004_M8005", 179 | "start": 1250.22, 180 | "end": 1470.57 181 | }, 182 | { 183 | "session": "R8004_M8005", 184 | "start": 1470.48, 185 | "end": 1662.19 186 | }, 187 | { 188 | "session": "R8004_M8005", 189 | "start": 1661.43, 190 | "end": 1865.88 191 | }, 192 | { 193 | "session": "R8004_M8005", 194 | "start": 1865.42, 195 | "end": 2088.92 196 | }, 197 | { 198 | "session": "R8004_M8006", 199 | "start": 0, 200 | "end": 329.19 201 | }, 202 | { 203 | "session": "R8004_M8006", 204 | "start": 328.89, 205 | "end": 560.13 206 | }, 207 | { 208 | "session": "R8004_M8006", 209 | "start": 559.36, 210 | "end": 771.8 211 | }, 212 | { 213 | "session": "R8004_M8006", 214 | "start": 771.36, 215 | "end": 1043.61 216 | }, 217 | { 218 | "session": "R8004_M8006", 219 | "start": 1043.4, 220 | "end": 1249.26 221 | }, 222 | { 223 | "session": "R8004_M8006", 224 | "start": 1248.88, 225 | "end": 1463.02 226 | }, 227 | { 228 | "session": "R8004_M8006", 229 | "start": 1462.81, 230 | "end": 1648.84 231 | }, 232 | { 233 | "session": "R8004_M8006", 234 | "start": 1647.88, 235 | "end": 1963.75 236 | }, 237 | { 238 | "session": "R8009_M8027", 239 | "start": 0, 240 | "end": 182.37 241 | }, 242 | { 243 | "session": "R8009_M8027", 244 | "start": 182.37, 245 | "end": 363.85 246 | }, 247 | { 248 | "session": "R8009_M8027", 249 | "start": 363.85, 250 | "end": 548.85 251 | }, 252 | { 253 | "session": "R8009_M8027", 254 | "start": 548.42, 255 | "end": 733.05 256 | }, 257 | { 258 | "session": "R8009_M8027", 259 | "start": 731.69, 260 | "end": 912.72 261 | }, 262 | { 263 | "session": "R8009_M8027", 264 | "start": 912.72, 265 | "end": 1097.34 266 | }, 267 | { 268 | "session": "R8009_M8027", 269 | "start": 1096.07, 270 | "end": 1278.06 271 | }, 272 | { 273 | "session": "R8009_M8027", 274 | "start": 1278.06, 275 | "end": 1462.43 276 | }, 277 | { 278 | "session": "R8009_M8027", 279 | "start": 1461.95, 280 | "end": 1644.15 281 | }, 282 | { 283 | "session": "R8009_M8027", 284 | "start": 1642.7, 285 | "end": 1879.63 286 | }, 287 | { 288 | "session": "R8009_M8024", 289 | "start": 0, 290 | "end": 184.11 291 | }, 292 | { 293 | "session": "R8009_M8024", 294 | "start": 183.56, 295 | "end": 365.43 296 | }, 297 | { 298 | "session": "R8009_M8024", 299 | "start": 364.88, 300 | "end": 546.45 301 | }, 302 | { 303 | "session": "R8009_M8024", 304 | "start": 546.14, 305 | "end": 727.23 306 | }, 307 | { 308 | "session": "R8009_M8024", 309 | "start": 726.59, 310 | "end": 908.34 311 | }, 312 | { 313 | "session": "R8009_M8024", 314 | "start": 907.22, 315 | "end": 1091.55 316 | }, 317 | { 318 | "session": "R8009_M8024", 319 | "start": 1090.01, 320 | "end": 1273.62 321 | }, 322 | { 323 | "session": "R8009_M8024", 324 | "start": 1272.41, 325 | "end": 1454.62 326 | }, 327 | { 328 | "session": "R8009_M8024", 329 | "start": 1454.62, 330 | "end": 1639.38 331 | }, 332 | { 333 | "session": "R8009_M8024", 334 | "start": 1638.23, 335 | "end": 1840.57 336 | }, 337 | { 338 | "session": "R8009_M8022", 339 | "start": 0, 340 | "end": 183.12 341 | }, 342 | { 343 | "session": "R8009_M8022", 344 | "start": 182.93, 345 | "end": 363.42 346 | }, 347 | { 348 | "session": "R8009_M8022", 349 | "start": 363.02, 350 | "end": 545.78 351 | }, 352 | { 353 | "session": "R8009_M8022", 354 | "start": 545.78, 355 | "end": 730.48 356 | }, 357 | { 358 | "session": "R8009_M8022", 359 | "start": 730.16, 360 | "end": 913.97 361 | }, 362 | { 363 | "session": "R8009_M8022", 364 | "start": 913.97, 365 | "end": 1096.66 366 | }, 367 | { 368 | "session": "R8009_M8022", 369 | "start": 1096.66, 370 | "end": 1276.69 371 | }, 372 | { 373 | "session": "R8009_M8022", 374 | "start": 1276.69, 375 | "end": 1457.45 376 | }, 377 | { 378 | "session": "R8009_M8022", 379 | "start": 1457.45, 380 | "end": 1642.21 381 | }, 382 | { 383 | "session": "R8009_M8022", 384 | "start": 1642.21, 385 | "end": 1824.48 386 | }, 387 | { 388 | "session": "R8009_M8022", 389 | "start": 1823.57, 390 | "end": 2013.66 391 | }, 392 | { 393 | "session": "R8009_M8028", 394 | "start": 0, 395 | "end": 182.19 396 | }, 397 | { 398 | "session": "R8009_M8028", 399 | "start": 181.4, 400 | "end": 362.0 401 | }, 402 | { 403 | "session": "R8009_M8028", 404 | "start": 362.0, 405 | "end": 542.73 406 | }, 407 | { 408 | "session": "R8009_M8028", 409 | "start": 542.42, 410 | "end": 724.67 411 | }, 412 | { 413 | "session": "R8009_M8028", 414 | "start": 724.67, 415 | "end": 906.93 416 | }, 417 | { 418 | "session": "R8009_M8028", 419 | "start": 906.62, 420 | "end": 1091.07 421 | }, 422 | { 423 | "session": "R8009_M8028", 424 | "start": 1090.97, 425 | "end": 1272.42 426 | }, 427 | { 428 | "session": "R8009_M8028", 429 | "start": 1271.99, 430 | "end": 1456.71 431 | }, 432 | { 433 | "session": "R8009_M8028", 434 | "start": 1456.31, 435 | "end": 1638.09 436 | }, 437 | { 438 | "session": "R8009_M8028", 439 | "start": 1637.21, 440 | "end": 1919.38 441 | }, 442 | { 443 | "session": "R8008_M8017", 444 | "start": 0, 445 | "end": 192.58 446 | }, 447 | { 448 | "session": "R8008_M8017", 449 | "start": 192.58, 450 | "end": 382.77 451 | }, 452 | { 453 | "session": "R8008_M8017", 454 | "start": 382.77, 455 | "end": 567.17 456 | }, 457 | { 458 | "session": "R8008_M8017", 459 | "start": 565.41, 460 | "end": 746.45 461 | }, 462 | { 463 | "session": "R8008_M8017", 464 | "start": 743.69, 465 | "end": 924.19 466 | }, 467 | { 468 | "session": "R8008_M8017", 469 | "start": 923.93, 470 | "end": 1106.88 471 | }, 472 | { 473 | "session": "R8008_M8017", 474 | "start": 1105.7, 475 | "end": 1286.49 476 | }, 477 | { 478 | "session": "R8008_M8017", 479 | "start": 1282.34, 480 | "end": 1463.97 481 | }, 482 | { 483 | "session": "R8008_M8017", 484 | "start": 1463.5, 485 | "end": 1818.27 486 | }, 487 | { 488 | "session": "R8009_M8021", 489 | "start": 0, 490 | "end": 186.69 491 | }, 492 | { 493 | "session": "R8009_M8021", 494 | "start": 186.56, 495 | "end": 371.47 496 | }, 497 | { 498 | "session": "R8009_M8021", 499 | "start": 371.47, 500 | "end": 553.97 501 | }, 502 | { 503 | "session": "R8009_M8021", 504 | "start": 553.97, 505 | "end": 735.21 506 | }, 507 | { 508 | "session": "R8009_M8021", 509 | "start": 735.08, 510 | "end": 916.53 511 | }, 512 | { 513 | "session": "R8009_M8021", 514 | "start": 916.34, 515 | "end": 1098.38 516 | }, 517 | { 518 | "session": "R8009_M8021", 519 | "start": 1097.54, 520 | "end": 1281.66 521 | }, 522 | { 523 | "session": "R8009_M8021", 524 | "start": 1278.74, 525 | "end": 1459.98 526 | }, 527 | { 528 | "session": "R8009_M8021", 529 | "start": 1459.43, 530 | "end": 1644.01 531 | }, 532 | { 533 | "session": "R8009_M8021", 534 | "start": 1642.97, 535 | "end": 2001.68 536 | }, 537 | { 538 | "session": "R8008_M8014", 539 | "start": 0, 540 | "end": 186.54 541 | }, 542 | { 543 | "session": "R8008_M8014", 544 | "start": 186.5, 545 | "end": 368.85 546 | }, 547 | { 548 | "session": "R8008_M8014", 549 | "start": 368.62, 550 | "end": 553.5 551 | }, 552 | { 553 | "session": "R8008_M8014", 554 | "start": 552.99, 555 | "end": 736.91 556 | }, 557 | { 558 | "session": "R8008_M8014", 559 | "start": 736.3, 560 | "end": 922.41 561 | }, 562 | { 563 | "session": "R8008_M8014", 564 | "start": 922.41, 565 | "end": 1109.52 566 | }, 567 | { 568 | "session": "R8008_M8014", 569 | "start": 1109.38, 570 | "end": 1297.43 571 | }, 572 | { 573 | "session": "R8008_M8014", 574 | "start": 1297.43, 575 | "end": 1480.32 576 | }, 577 | { 578 | "session": "R8008_M8014", 579 | "start": 1480.32, 580 | "end": 1664.66 581 | }, 582 | { 583 | "session": "R8008_M8014", 584 | "start": 1664.66, 585 | "end": 1936.0 586 | }, 587 | { 588 | "session": "R8005_M8009", 589 | "start": 0, 590 | "end": 195.03 591 | }, 592 | { 593 | "session": "R8005_M8009", 594 | "start": 193.86, 595 | "end": 458.62 596 | }, 597 | { 598 | "session": "R8005_M8009", 599 | "start": 458.62, 600 | "end": 750.09 601 | }, 602 | { 603 | "session": "R8005_M8009", 604 | "start": 749.89, 605 | "end": 938.63 606 | }, 607 | { 608 | "session": "R8005_M8009", 609 | "start": 937.47, 610 | "end": 1154.11 611 | }, 612 | { 613 | "session": "R8005_M8009", 614 | "start": 1153.99, 615 | "end": 1374.63 616 | }, 617 | { 618 | "session": "R8005_M8009", 619 | "start": 1373.6, 620 | "end": 1554.07 621 | }, 622 | { 623 | "session": "R8005_M8009", 624 | "start": 1553.65, 625 | "end": 1773.28 626 | }, 627 | { 628 | "session": "R8006_M8012", 629 | "start": 0, 630 | "end": 187.72 631 | }, 632 | { 633 | "session": "R8006_M8012", 634 | "start": 186.62, 635 | "end": 372.14 636 | }, 637 | { 638 | "session": "R8006_M8012", 639 | "start": 372.01, 640 | "end": 577.46 641 | }, 642 | { 643 | "session": "R8006_M8012", 644 | "start": 577.19, 645 | "end": 760.98 646 | }, 647 | { 648 | "session": "R8006_M8012", 649 | "start": 760.65, 650 | "end": 949.37 651 | }, 652 | { 653 | "session": "R8006_M8012", 654 | "start": 948.59, 655 | "end": 1166.63 656 | }, 657 | { 658 | "session": "R8006_M8012", 659 | "start": 1166.39, 660 | "end": 1353.13 661 | }, 662 | { 663 | "session": "R8006_M8012", 664 | "start": 1352.75, 665 | "end": 1547.93 666 | }, 667 | { 668 | "session": "R8006_M8012", 669 | "start": 1547.33, 670 | "end": 1826.86 671 | }, 672 | { 673 | "session": "R8005_M8007", 674 | "start": 0, 675 | "end": 194.04 676 | }, 677 | { 678 | "session": "R8005_M8007", 679 | "start": 193.42, 680 | "end": 438.33 681 | }, 682 | { 683 | "session": "R8005_M8007", 684 | "start": 438.0, 685 | "end": 625.73 686 | }, 687 | { 688 | "session": "R8005_M8007", 689 | "start": 625.51, 690 | "end": 862.7 691 | }, 692 | { 693 | "session": "R8005_M8007", 694 | "start": 862.06, 695 | "end": 1074.15 696 | }, 697 | { 698 | "session": "R8005_M8007", 699 | "start": 1073.43, 700 | "end": 1269.17 701 | }, 702 | { 703 | "session": "R8005_M8007", 704 | "start": 1269.17, 705 | "end": 1464.63 706 | }, 707 | { 708 | "session": "R8005_M8007", 709 | "start": 1463.13, 710 | "end": 1668.1 711 | }, 712 | { 713 | "session": "R8005_M8007", 714 | "start": 1668.06, 715 | "end": 1863.2 716 | }, 717 | { 718 | "session": "R8009_M8026", 719 | "start": 0, 720 | "end": 181.39 721 | }, 722 | { 723 | "session": "R8009_M8026", 724 | "start": 181.39, 725 | "end": 361.59 726 | }, 727 | { 728 | "session": "R8009_M8026", 729 | "start": 361.04, 730 | "end": 542.07 731 | }, 732 | { 733 | "session": "R8009_M8026", 734 | "start": 542.0, 735 | "end": 723.81 736 | }, 737 | { 738 | "session": "R8009_M8026", 739 | "start": 723.23, 740 | "end": 904.44 741 | }, 742 | { 743 | "session": "R8009_M8026", 744 | "start": 903.95, 745 | "end": 1088.28 746 | }, 747 | { 748 | "session": "R8009_M8026", 749 | "start": 1085.96, 750 | "end": 1267.14 751 | }, 752 | { 753 | "session": "R8009_M8026", 754 | "start": 1266.98, 755 | "end": 1448.95 756 | }, 757 | { 758 | "session": "R8009_M8026", 759 | "start": 1448.95, 760 | "end": 1630.36 761 | }, 762 | { 763 | "session": "R8009_M8026", 764 | "start": 1630.36, 765 | "end": 1980.28 766 | }, 767 | { 768 | "session": "R8002_M8003", 769 | "start": 0, 770 | "end": 182.13 771 | }, 772 | { 773 | "session": "R8002_M8003", 774 | "start": 181.34, 775 | "end": 369.51 776 | }, 777 | { 778 | "session": "R8002_M8003", 779 | "start": 369.15, 780 | "end": 549.95 781 | }, 782 | { 783 | "session": "R8002_M8003", 784 | "start": 549.57, 785 | "end": 736.93 786 | }, 787 | { 788 | "session": "R8002_M8003", 789 | "start": 736.85, 790 | "end": 924.49 791 | }, 792 | { 793 | "session": "R8002_M8003", 794 | "start": 924.37, 795 | "end": 1105.33 796 | }, 797 | { 798 | "session": "R8002_M8003", 799 | "start": 1104.04, 800 | "end": 1294.8 801 | }, 802 | { 803 | "session": "R8002_M8003", 804 | "start": 1294.22, 805 | "end": 1474.22 806 | }, 807 | { 808 | "session": "R8002_M8003", 809 | "start": 1474.04, 810 | "end": 1663.04 811 | }, 812 | { 813 | "session": "R8002_M8003", 814 | "start": 1662.95, 815 | "end": 1846.95 816 | }, 817 | { 818 | "session": "R8002_M8003", 819 | "start": 1846.95, 820 | "end": 2037.53 821 | }, 822 | { 823 | "session": "R8009_M8025", 824 | "start": 0, 825 | "end": 182.24 826 | }, 827 | { 828 | "session": "R8009_M8025", 829 | "start": 181.73, 830 | "end": 365.36 831 | }, 832 | { 833 | "session": "R8009_M8025", 834 | "start": 365.36, 835 | "end": 548.35 836 | }, 837 | { 838 | "session": "R8009_M8025", 839 | "start": 548.35, 840 | "end": 729.27 841 | }, 842 | { 843 | "session": "R8009_M8025", 844 | "start": 728.45, 845 | "end": 910.11 846 | }, 847 | { 848 | "session": "R8009_M8025", 849 | "start": 909.86, 850 | "end": 1090.05 851 | }, 852 | { 853 | "session": "R8009_M8025", 854 | "start": 1089.74, 855 | "end": 1274.43 856 | }, 857 | { 858 | "session": "R8009_M8025", 859 | "start": 1274.21, 860 | "end": 1457.63 861 | }, 862 | { 863 | "session": "R8009_M8025", 864 | "start": 1457.63, 865 | "end": 1639.8 866 | }, 867 | { 868 | "session": "R8009_M8025", 869 | "start": 1639.8, 870 | "end": 1819.85 871 | }, 872 | { 873 | "session": "R8009_M8025", 874 | "start": 1819.85, 875 | "end": 2059.69 876 | }, 877 | { 878 | "session": "R8005_M8008", 879 | "start": 0, 880 | "end": 217.68 881 | }, 882 | { 883 | "session": "R8005_M8008", 884 | "start": 217.1, 885 | "end": 407.51 886 | }, 887 | { 888 | "session": "R8005_M8008", 889 | "start": 407.51, 890 | "end": 588.95 891 | }, 892 | { 893 | "session": "R8005_M8008", 894 | "start": 587.89, 895 | "end": 786.32 896 | }, 897 | { 898 | "session": "R8005_M8008", 899 | "start": 785.78, 900 | "end": 966.28 901 | }, 902 | { 903 | "session": "R8005_M8008", 904 | "start": 966.18, 905 | "end": 1146.67 906 | }, 907 | { 908 | "session": "R8005_M8008", 909 | "start": 1146.39, 910 | "end": 1345.6 911 | }, 912 | { 913 | "session": "R8005_M8008", 914 | "start": 1345.21, 915 | "end": 1535.61 916 | }, 917 | { 918 | "session": "R8005_M8008", 919 | "start": 1534.58, 920 | "end": 1731.77 921 | }, 922 | { 923 | "session": "R8005_M8008", 924 | "start": 1731.34, 925 | "end": 1942.27 926 | }, 927 | { 928 | "session": "R8002_M8002", 929 | "start": 0, 930 | "end": 249.06 931 | }, 932 | { 933 | "session": "R8002_M8002", 934 | "start": 248.92, 935 | "end": 439.54 936 | }, 937 | { 938 | "session": "R8002_M8002", 939 | "start": 439.02, 940 | "end": 626.0 941 | }, 942 | { 943 | "session": "R8002_M8002", 944 | "start": 625.95, 945 | "end": 812.29 946 | }, 947 | { 948 | "session": "R8002_M8002", 949 | "start": 812.03, 950 | "end": 999.25 951 | }, 952 | { 953 | "session": "R8002_M8002", 954 | "start": 998.82, 955 | "end": 1183.93 956 | }, 957 | { 958 | "session": "R8002_M8002", 959 | "start": 1183.84, 960 | "end": 1383.98 961 | }, 962 | { 963 | "session": "R8002_M8002", 964 | "start": 1383.72, 965 | "end": 1570.45 966 | }, 967 | { 968 | "session": "R8002_M8002", 969 | "start": 1570.19, 970 | "end": 1763.88 971 | }, 972 | { 973 | "session": "R8002_M8002", 974 | "start": 1763.76, 975 | "end": 2059.15 976 | } 977 | ] 978 | --------------------------------------------------------------------------------