import json
from difflib import Differ
import pysubs2
import keyboard
import ffmpeg
import shutil
import tempfile
import warnings
import os
from pathlib import Path
import time
import aiohttp
import asyncio
from Audio_Functions import audio_transcribe
#target duration routine
#video2 array add
#add buffer of 1 second after the last word
#score for last word buffer
import whisper_timestamped as whisper
from whisper_timestamped.make_subtitles import write_srt
from Audio_subtitle_align import *
from Podcast_Functions import podcast_snipet_array
from Video_Functions import video_chunk,video_concat_moviepy,media_duration,is_video
from Explorer_Functions import is_file_in_dir
from Subtitles_Functions import vtt_to_ass
import base64
podcast_dir = 'G:/My Drive/Shared/FarmNetz/Podcast/'

#adjust for no edit and enter to continue
#save objects to the folder for reprocessing

#chunks and vtt to text text to vtt
 # Set true if you're using huggingface inference API API https://huggingface.co/inference-api
API_BACKEND = False
remote_threshold = 180
# MODEL = 'facebook/wav2vec2-large-960h-lv60-self'
# MODEL  = "facebook/wav2vec2-large-960h"
MODEL = "facebook/wav2vec2-base-960h"
# MODEL = "patrickvonplaten/wav2vec2-large-960h-lv60-self-4-gram"
notepad_bin = 'C:/Program Files (x86)/Notepad++/notepad++.exe'



if API_BACKEND:
    HF_TOKEN = '<TRUFFLEHOG_REDACTED_HUGGINGFACE>'
    headers = {"Authorization": f"Bearer {HF_TOKEN}"}
    API_URL = f'https://api-inference.huggingface.co/models/{MODEL}'

else:
    import torch
    from transformers import pipeline
    # is cuda available?
    cuda = torch.device(
        'cuda:0') if torch.cuda.is_available() else torch.device('cpu')
    device = 0 if torch.cuda.is_available() else -1
    speech_recognizer = pipeline(
        task="automatic-speech-recognition",
        model=f'{MODEL}',
        tokenizer=f'{MODEL}',
        framework="pt",
        device=device,
    )

async def wait_for_file_modification(file_path):
    start_time = time.time()
    print("Start time:", start_time)
    while not os.path.getmtime(file_path) > start_time:
        #press esc to continue
        print("Press esc if no edit is needed for file:", file_path)
        if keyboard.is_pressed('esc'):
            print("Esc pressed, continuing")
            break
        time.sleep(5)
    return file_path




async def speech_to_text(video_file_path,remote=False,language_code='en'):
    
    """
    Takes a video path to convert to audio, transcribe audio channel to text and word  timestamps usiing whisper timestamp
    """
    
    
    if (video_file_path == None):
        raise ValueError("Error no video input")

    video_path = Path(video_file_path)
    
    try:
        # convert video to audio 16k using PIPE to audio_memory
        audio_memory, _ = ffmpeg.input(video_path).output(
            '-', format="wav", ac=1, ar='16k').overwrite_output().global_args('-loglevel', 'quiet').run(capture_stdout=True)
        #output to a temp file
        with tempfile.NamedTemporaryFile(delete=False, suffix='.wav') as temp_audio_file:
            temp_audio_file.write(audio_memory)
        #get tje file duration
        
        print(f"Audio written to temporary file: {temp_audio_file.name}")
    except Exception as e:
        raise RuntimeError("Error converting video to audio")
    

    ping("speech_to_text")
    
    try:
        if remote == False:
            print(f'Transcribing via whisper locally')
            model = whisper.load_model("small", device="cuda")
            audio = whisper.load_audio(temp_audio_file.name)
            warnings.filterwarnings("ignore", category=UserWarning, module="whisper")
            #result = whisper.transcribe(model, audio, language=language_code, detect_disfluencies=True)
            result = whisper.transcribe(model, audio, detect_disfluencies=True)
            #write_srt(result["segments"], open("file.srt", "w", encoding = "utf8"))
            write_srt(result["segments"], open(srt_fullpath, "w", encoding = "windows-1252"))
            print(result)

            #transcription = result["text"].lower()
            transcription = " ".join(word["text"].lower().strip(',') for segment in result['segments'] for word in segment['words'])
            #print(transcription)
            
            timestamps = [[word["text"].lower().strip(','), word["start"], word["end"]] for segment in result['segments'] for word in segment['words']]
            return (transcription, transcription, timestamps)
        elif remote == True:
            #transcripbe with whisper
            print(f'Transcribing via whisper remotely')
            language = language_code
            model, align_model_metadata = load_align_model(language, device="cuda")
            audio_fullpath_unix = temp_audio_file.name
            audio_fullpath_unix = audio_fullpath_unix.replace("\\", "/")
            vtt_fullpath = audio_transcribe(audio_fullpath_unix)
            ass_fullpath = vtt_to_ass(vtt_fullpath,video_fullpath = video_file_path)
            print(ass_fullpath)
            segments = parse_ass_file(Path(ass_fullpath))
            # Align
            audio = load_audio(audio_fullpath_unix)
            result = align(segments, model, align_model_metadata, audio, device="cuda")
            #print the keys
            print(result.keys())

            #print(result['segments'])
            write_srt(result["segments"], open(srt_fullpath, "w", encoding = "windows-1252"))
            #input("Press Enter to continue...")
            #loop all the keys in the result
            print(result)


            #transcription = " ".join(word["word"].lower().strip(',') for segment in result['segments'] for word in segment['words'])
            #print(transcription)
            transcription = " ".join(word["word"].lower().strip(',') for segment in result['segments'] for word in segment['words'] if "start" in word and "end" in word)
            print(transcription)

            # Generate timestamps only for words that have both 'start' and 'end' attributes
            #timestamps = [[word["word"].lower().strip(','), word["start"], word["end"]] for segment in result['segments'] for word in segment['words']]
            timestamps = [[word["word"].lower().strip(','), word["start"], word["end"]] for segment in result['segments'] for word in segment['words'] if "start" in word and "end" in word]


            print(timestamps)
            return (transcription, transcription, timestamps)
    except Exception as e:
        raise RuntimeError("Error Running inference with local model", e)

    






async def cut_timestamps_to_video_char(video_in, transcription, text_in, timestamps,video_out):
    """
    Given original video input, text transcript + timestamps,
    and edit ext cuts video segments into a single video
    """
    media_duration_var = media_duration(video_in)

    video_path = Path(video_in)
    video_file_name = video_path.stem
    if (video_in == None or text_in == None or transcription == None):
        raise ValueError("Inputs undefined")

    d = Differ()
    # compare original transcription with edit text
    diff_chars = d.compare(transcription, text_in)
    print(diff_chars)
    
    
    # remove all text aditions from diff
    filtered = list(filter(lambda x: x[0] != '+', diff_chars))

    # filter timestamps to be removed
    # timestamps_to_cut = [b for (a,b) in zip(filtered, timestamps_var) if a[0]== '-' ]
    # return diff tokes and cutted video!!

    # groupping character timestamps so there are less cuts
    idx = 0
    grouped = {}
    for (a, b) in zip(filtered, timestamps):
        if a[0] != '-':
            if idx in grouped:
                grouped[idx].append(b)
            else:
                grouped[idx] = []
                grouped[idx].append(b)
        else:
            idx += 1
    #the last word should have a buffer until the start of the next word

    
    # after grouping, gets the lower and upter start and time for each group
    try:
        timestamps_to_cut = [[v[0][1], v[-1][2]] for v in grouped.values()]
        between_str = '+'.join(
        map(lambda t: f'between(t,{t[0]},{t[1]})', timestamps_to_cut))
        video_file = ffmpeg.input(video_in)
        video = video_file.video.filter(
            "select", f'({between_str})').filter("setpts", "N/FRAME_RATE/TB")
        audio = video_file.audio.filter(
            "aselect", f'({between_str})').filter("asetpts", "N/SR/TB")
        ffmpeg.concat(video, audio, v=1, a=1).output(
            video_out).overwrite_output().global_args('-loglevel', 'quiet').run()
        tokens = [(token[2:], token[0] if token[0] != " " else None) for token in filtered]
        return (tokens, video_out)
    except:
        video_out = video_in
        return (None, video_out)

async def cut_timestamps_to_video_word(loop,video_in, transcription, timestamps,video2_array):
    """
    Given original video input, text transcript + timestamps,
    and edit ext cuts video segments into a single video
    """
    media_duration_var = media_duration(video_in)

    print("Target duration:", target_duration)
    output_duration = media_duration_var + 1
    if target_duration:
        chunk_target_duration = target_duration
    else:
        chunk_target_duration = media_duration_var
    
    while output_duration > chunk_target_duration:
        
        with open(text_out, 'r') as f:
            text_in = f.read()
        
        print("Transcription______________________")
        print(transcription)
        print("Text input_______________________________")
        print(text_in)
        #input("Press Enter to continue...")
        if text_in == '':
            #remove this part
            print("Text input is empty")
            return
        if text_in == transcription:
            video_out = video_in
            print("No edit, no need to cut")
            return

        
        if (video_in == None or text_in == None or transcription == None):
            raise ValueError("Inputs undefined")

        d = Differ()
        # compare original transcription with edit text
        #diff_chars = d.compare(transcription, text_in)
        
        print("filtered_______________________________")
        
        # remove all text aditions from diff and spaces at the word level
        #compare word by word
        diff_words = d.compare(transcription.split(), text_in.split())
        filtered = list(filter(lambda x: x[0] != '+', diff_words))
        #filtered = list(filter(lambda x: x[0] != '+' and x[0] != '-', diff_words))
        
        print(filtered)

        
        idx = 0
        grouped = {}
        for (a, b) in zip(filtered, timestamps):
            # Strip leading spaces from 'a' before checking
            stripped_a = a.strip()
            if stripped_a and stripped_a[0] != '-':
                if idx not in grouped:
                    grouped[idx] = []
                grouped[idx].append(b)
            else:
                idx += 1
        '''
        for (a, b) in zip(filtered, timestamps):
            if '-' not in a[0]:
                print(f"a[0]: {a[0]}")
                print(f"a: {a}")
                if idx in grouped:
                    grouped[idx].append(b)
                else:
                    grouped[idx] = []
                    grouped[idx].append(b)
            else:
                idx += 1
        '''

        #this should check the last word and if it's the last word, it should make a cut until the end of the video
    
        
        print("grouped_______________________________")
        print(grouped)

        #the last word should have a buffer until the start of the next word
        #find the index of the last word of the grrouped object from the timestamps object
        try:
            last_grouped_word = grouped[max(grouped.keys())][-1]
            last_grouped_word_index = timestamps.index(last_grouped_word)
            print(last_grouped_word)
            #get the next word
            last_next_word = timestamps[last_grouped_word_index + 1]
            last_next_word = timestamps[last_grouped_word_index + 1]
            print(last_next_word)

            #get the end time of the last word
            last_next_word_start_time = last_next_word[1]
            #replace the end time of the last word with the start time of the next word
            print("Last next word start time:", last_next_word_start_time)
            #now in the grouped object, get the last word and replace the end time with the start time of the next word
            grouped[max(grouped.keys())][-1][2] = last_next_word_start_time
        except IndexError:
            print("No next word, no adjustement beeded")
            
        

        # after grouping, gets the lower and upter start and time for each group
        
        timestamps_to_cut = [[v[0][1], v[-1][2]] for v in grouped.values()]
        print("Timestamps to cut_______________________________")
        print(timestamps_to_cut)
        #estimated total duration
        output_duration = sum([v[1] - v[0] for v in timestamps_to_cut])
        print("Out duration:", output_duration)
        if output_duration > chunk_target_duration:
            
            start_time = time.time()
            print(f"Output duration {output_duration} > chunk target duration {chunk_target_duration}")
            #wait_for_file_modification(text_out, start_time)
            while not os.path.getmtime(text_out) > start_time:
                time.sleep(1)
            print("File modified, re-running the loop")
            





    between_str = '+'.join(
        map(lambda t: f'between(t,{t[0]},{t[1]})', timestamps_to_cut))
    print("Between str_______________________________")
    print(between_str)

    video_cut_array = []
    video_cut_array.append(video_in)
    if video2_array:
        video_cut_array += video2_array
    print("Video cut array_______________________________")
    print(video_cut_array)
    if timestamps_to_cut:
        for video_cut in video_cut_array:
            video_out = video_cut.replace('.' + video_ext,'_edit.' + video_ext )
            is_video_var = is_video(video_cut)
            if is_video_var:
                video_file = ffmpeg.input(video_cut)
                video = video_file.video.filter(
                    "select", f'({between_str})').filter("setpts", "N/FRAME_RATE/TB")
                audio = video_file.audio.filter(
                    "aselect", f'({between_str})').filter("asetpts", "N/SR/TB")
                #ffmpeg.concat(video, audio, v=1, a=1).output(video_out).overwrite_output().global_args('-c:v', 'h264_nvenc').global_args('-loglevel', 'quiet').run()
                #copy the audio and video codec
                ffmpeg.concat(video, audio, v=1, a=1).output(video_out).overwrite_output().global_args('-c:v', 'copy').global_args('-c:a', 'copy').global_args('-loglevel', 'quiet').run()
                
            else:
                audio_file = ffmpeg.input(video_cut)
                audio = audio_file.audio.filter(
                    "aselect", f'({between_str})').filter("asetpts", "N/SR/TB")
                ffmpeg.output(audio, video_out).overwrite_output().global_args('-loglevel', 'quiet').run()
            print("Video cut:", video_out)
            

    else:
        #no cuts needed
        video_out = video_in

    tokens = [(token[2:], token[0] if token[0] != " " else None)
            for token in filtered]

    ping("video_cuts")
    return (tokens, video_out)
    


async def query_api(audio_bytes: bytes):
    """
    Query for Huggingface Inference API for Automatic Speech Recognition task
    """
    payload = json.dumps({
        "inputs": base64.b64encode(audio_bytes).decode("utf-8"),
        "parameters": {
            #"return_timestamps": "char",
            "chunk_length_s": 10,
            "stride_length_s": [4, 2]
        },
        "options": {"use_gpu": False}
    }).encode("utf-8")
    async with aiohttp.ClientSession() as session:
        async with session.post(API_URL, headers=headers, data=payload) as response:
            print("API Response: ", response.status)
            if response.headers['Content-Type'] == 'application/json':
                return await response.json()
            elif response.headers['Content-Type'] == 'application/octet-stream':
                return await response.read()
            elif response.headers['Content-Type'] == 'text/plain':
                return await response.text()
            else:
                raise RuntimeError("Error Fetching API")


def ping(name):
    url = f'https://huggingface.co/api/telemetry/spaces/radames/edit-video-by-editing-text/{name}'
    print("ping: ", url)

    async def req():
        async with aiohttp.ClientSession() as session:
            async with session.get(url) as response:
                print("pong: ", response.status)
    asyncio.create_task(req())



def main():
    global srt_fullpath
    import argparse
    import subprocess
    
    global target_duration
    global text_out
    global full_media_duration
    global language_code
    global video_ext
    parser = argparse.ArgumentParser(description="Edit video by editing text")
    parser.add_argument("video_file", help="Input video file")
    #parser.add_argument("target_duration", help="Target duration of the output video", type=int,default=None)
    parser.add_argument("language_code", help="Language of the video", type=str, nargs='?', default='en')
    parser.add_argument("target_duration", help="Target duration of the output video", type=int, nargs='?', default=None)
    parser.add_argument("video_file2", help="Secondary video files", nargs='?', default=None)
    
    chunk_duration = 300
    is_processed = False
    
    #make that optional
    args = parser.parse_args()
    print(args)
    video_file_path = args.video_file
    
    print(f"Processing video: {video_file_path}")
    video_ext = video_file_path.split('.')[-1]
    
    #make video_fule2 an array if there is a comma split
    if args.video_file2:
        video_file2_array = args.video_file2.split(',')
        print(video_file2_array)
    else:
        #if it's the podcast directory then get all the files in the directory in the array
        #'G:/My Drive/Shared/FarmNetz/Podcast' if it's inside this directory
        #if 'FarmNetz/Podcast' in video_file_path and 'snipet' in video_file_path:
        if is_file_in_dir(video_file_path, podcast_dir) and 'snipet' in video_file_path:
            print("This is a podcast directory")
            video_file2_array = podcast_snipet_array(video_file_path)
            #input(video_file2_array)
            #this is main file G:\My Drive\Shared\FarmNetz\Podcast\Season 2\Noah\main_snipet1.mp4 and you should get G:\My Drive\Shared\FarmNetz\Podcast\Season 2\Noah\split1_snipet1.mp4 and split0
        else:
            video_file2_array = []
    
    full_media_duration = media_duration(video_file_path)
    if full_media_duration > remote_threshold:
        #zusing remote
        remote = True
        print(f"Video duration is greater than {remote_threshold} seconds, using remote")
    else:
        remote = False
    target_duration = args.target_duration
    if target_duration:
        print("Received target duration: ", target_duration)
    
    language_code = args.language_code
    
    
    video_full_out = video_file_path.replace('.' + video_ext,'_edit.' + video_ext)
    #check if json file exists
    print("Full media duration:", full_media_duration)
    chunk_number = int(full_media_duration/chunk_duration) + 1
    print("Chunk length:", chunk_number)
    chunk_dir = os.path.join(os.path.dirname(video_file_path), 'edit_chunks')
    
    #check if there are both json and timestamp files
    for i in range(chunk_number):
        #G:\My Drive\Shared\FarmNetz\Podcast\BLUE\edit_chunks\hannah reid snipet_000.srt
        chunk_index = str(i).zfill(3)
        #G:\My Drive\Shared\FarmNetz\Podcast\BLUE\edit_chunks\hannah reid snipet_000.json
        #G:\My Drive\Shared\FarmNetz\Podcast\BLUE\hannah reid snipet_000.json
        transcription_json_fullpath_check = os.path.join(chunk_dir, os.path.basename(video_file_path).replace('.' + video_ext,'_' + str(chunk_index) + '.json'))
        print(transcription_json_fullpath_check)
        print(os.path.exists(transcription_json_fullpath_check))
        #G:\My Drive\Shared\FarmNetz\Podcast\BLUE\edit_chunks\hannah reid snipet_000._timestamps.json
        timestamp_json_fullpath_check = os.path.join(chunk_dir, os.path.basename(video_file_path).replace('.' + video_ext,'_' + str(chunk_index) + '_timestamps.json'))
        print(timestamp_json_fullpath_check)
        print(os.path.exists(timestamp_json_fullpath_check))
        if os.path.exists(transcription_json_fullpath_check) and os.path.exists(timestamp_json_fullpath_check):
            print("Chunk exists")
            is_processed = True
            continue
        else:
            print("Chunk does not exist")
            is_processed = False
            break
    #input()
    if not is_processed:
        print("Chunking video")
        video_array = video_chunk(video_file_path,max_time_sec=300,chunk_dir='edit_chunks')
    else:
        print("Chunks already exist")
        video_array = [os.path.join(chunk_dir, os.path.basename(video_file_path).replace('.' + video_ext,'_' + str(i).zfill(3) + '.' + video_ext)) for i in range(chunk_number)]
    #print("Chunking suspended")
    #video_array = [video_file_path]
    out_edit_array = []
    for chunk_fullpath in video_array:
        video_ext = chunk_fullpath.split('.')[-1]
        srt_fullpath = chunk_fullpath.replace('.' + video_ext,'.srt')
        
        text_out = chunk_fullpath.replace(video_ext,'txt')
        transcription_json_fullpath = chunk_fullpath.replace(video_ext,'json')
        print("Transcription json:", transcription_json_fullpath)
        timestamp_json_fullpath = chunk_fullpath.replace('.' + video_ext,'_timestamps.json')
        print("Timestamp json:", timestamp_json_fullpath)
        

        print(f"Processing chunk: {chunk_fullpath}")
        loop = asyncio.get_event_loop()
        
        #check if there is already a data object in the folder
        if os.path.exists(transcription_json_fullpath):
            print("Transcription json exists")
            with open(transcription_json_fullpath, 'r') as f:
                transcription = json.load(f)
            with open(timestamp_json_fullpath, 'r') as f:
                timestamps = json.load(f)
        else:
            transcription, transcription, timestamps = loop.run_until_complete(speech_to_text(chunk_fullpath,remote=remote,language_code=language_code))

        #save the transcription to a json file
        with open(transcription_json_fullpath, 'w') as f:
            json.dump(transcription, f)

        with open(timestamp_json_fullpath, 'w') as f:
            json.dump(timestamps, f)



        #save that to json
        #save the transcription to a json file
    

        print("Transcription:", transcription)
        print("Timestamps:", timestamps)

        #save the transcript to a temp file and wait for enter 
        with open(text_out, 'w') as f:
            f.write(transcription)
        
        #open with notep
        subprocess.run([notepad_bin, text_out],shell=True)
        time.sleep(1)
        
        #wait for the file to have a mofidcation after the start time
        
        
        print("Text timestamp:", os.path.getmtime(text_out))
        

        print("Waiting for the file to be modified...")
        
        '''
        while not os.path.getmtime(text_out) > start_time:
            time.sleep(1)
        '''
        
        #loop.run_until_complete(wait_for_file_modification(srt_fullpath, start_time))
        loop.run_until_complete(wait_for_file_modification(text_out))
        print("File modified generating the output video")
        
            

        #load the edited text
        #edited_text = input("Add the edited text")
        '''

        subs = pysubs2.load(srt_fullpath)
        edited_text = " ".join([line.text for line in subs])
        #remove all ^\n 
        edited_text = " ".join([line.text.replace('\n', ' ').replace('\r', '').strip() for line in subs])
        #remove - from the text
        edited_text = edited_text.replace('-','')
        '''
        


        
        loop.run_until_complete(cut_timestamps_to_video_word(loop,chunk_fullpath, transcription, timestamps,video_file2_array))
        #elif level == 'char':
            #loop.run_until_complete(cut_timestamps_to_video_char(video_file_path, transcription, timestamps, video_out))
        video_out = chunk_fullpath.replace('.' + video_ext,'_edit.' + video_ext )

        if os.path.exists(video_out):
            out_edit_array.append(video_out)
            print("Output video:", video_out)
        else:
            print("No edit, no need to cut")
            out_edit_array.append(chunk_fullpath)
            print("Output video:", chunk_fullpath)
    
    print("All the chunks have been edited")
    print(out_edit_array)
    #replace \\ with / in all the video in the array
    out_edit_array = [video.replace("\\", "/") for video in out_edit_array]
    #concatenate the chunks
    if len(out_edit_array) > 1:
        video_concat_fullpath = video_concat_moviepy(out_edit_array)
        shutil.move(video_concat_fullpath,video_full_out)
    else:
        video_concat_fullpath = out_edit_array[0]
        shutil.copy(video_concat_fullpath,video_full_out)
    
    
    print("Final video:", video_full_out)

if __name__ == "__main__":
    main()



why do i keep getting the modified date after the start date. it seems the start time does not change when i re run the script