File size: 5,668 Bytes
80f5255
1f706a9
 
4daa7b9
1f706a9
 
 
 
 
2d45773
 
857662e
1f706a9
 
 
857662e
297d1e5
1f706a9
 
 
9de6643
1f706a9
297d1e5
0cfb3b7
2d45773
 
 
1f706a9
 
2d45773
1f706a9
2d45773
 
 
 
 
1f706a9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2d45773
1f706a9
2d45773
297d1e5
 
 
1f706a9
297d1e5
 
 
 
1f706a9
 
 
 
 
 
 
 
 
 
2d45773
1f706a9
 
 
 
 
2d45773
 
 
1f706a9
2d45773
 
 
 
1f706a9
2d45773
1f706a9
 
 
 
 
 
 
 
 
2d45773
1f706a9
 
 
2d45773
 
 
1f706a9
2d45773
 
1f706a9
2d45773
 
297d1e5
2d45773
0cfb3b7
 
1f706a9
 
 
0cfb3b7
1f706a9
 
 
 
0cfb3b7
1f706a9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import os
import re
import json
import torch
import requests
import unicodedata
import soundfile as sf
import pymorphy2

import gradio as gr
import wikipediaapi
from PIL import Image
from transformers import pipeline, CLIPProcessor, CLIPModel

morph = pymorphy2.MorphAnalyzer()

def load_attractions_json(url):
    r = requests.get(url)
    r.raise_for_status()
    return json.loads(r.text)

url = "https://raw.githubusercontent.com/nktssk/tourist-helper/refs/heads/main/landmarks.json"
landmark_titles = load_attractions_json(url)

def clean_text(text):
    text = re.sub(r'МФА:?\s?\[.*?\]', '', text)
    text = re.sub(r'\[.*?\]', '', text)
    def rm_diacritics(c):
        return '' if unicodedata.category(c) == 'Mn' else c
    text = unicodedata.normalize('NFD', text)
    text = ''.join(rm_diacritics(c) for c in text)
    text = unicodedata.normalize('NFC', text)
    text = re.sub(r'\s+', ' ', text)
    text = re.sub(r'[^\w\s.,!?-]', '', text)
    return text.strip()

# Упрощенное определение падежа по предлогу
def get_case_for_preposition(prep):
    d = {
        'в': 'loc', 'на': 'loc', 'о': 'loc', 'об': 'loc', 'обо': 'loc',
        'к': 'dat',
        'с': 'ins', 'со': 'ins', 'над': 'ins', 'под': 'ins',
        'из': 'gen', 'от': 'gen', 'у': 'gen', 'до': 'gen', 'для': 'gen'
    }
    return d.get(prep.lower(), 'nom')

def replace_numbers_with_text_in_context(text):
    tokens = text.split()
    result = []
    for i, token in enumerate(tokens):
        if re.match(r'^\d+(\.\d+)?$', token):
            cse = 'nom'
            if i > 0:
                cse = get_case_for_preposition(tokens[i - 1])
            # Сначала переводим число в текст (nominative)
            from num2words import num2words
            number_as_words = num2words(float(token) if '.' in token else int(token), lang='ru')
            number_as_words = number_as_words.replace('-', ' ')
            subtokens = number_as_words.split()
            inflected_subtokens = []
            for st in subtokens:
                p = morph.parse(st)
                if p:
                    best = p[0]
                    if cse in best.tag.cases:
                        form = best.inflect({cse})
                        inflected_subtokens.append(form.word if form else st)
                    else:
                        inflected_subtokens.append(st)
                else:
                    inflected_subtokens.append(st)
            result.append(' '.join(inflected_subtokens))
        else:
            result.append(token)
    return ' '.join(result)

summarizer = pipeline("summarization", model="facebook/bart-large-cnn")
translator = pipeline("translation_en_to_ru", model="Helsinki-NLP/opus-mt-en-ru")
wiki = wikipediaapi.Wikipedia("Nikita", "en")

clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
clip_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")

text_inputs = clip_processor(text=landmark_titles, images=None, return_tensors="pt", padding=True)
with torch.no_grad():
    text_embeds = clip_model.get_text_features(**text_inputs)
    text_embeds = text_embeds / text_embeds.norm(p=2, dim=-1, keepdim=True)

language = 'ru'
model_id = 'v3_1_ru'
sample_rate = 48000
speaker = 'eugene'
silero_model, _ = torch.hub.load(
    repo_or_dir='snakers4/silero-models',
    model='silero_tts',
    language=language,
    speaker=model_id
)

def text_to_speech(text, out_path="speech.wav"):
    text = replace_numbers_with_text_in_context(text)
    audio = silero_model.apply_tts(text=text, speaker=speaker, sample_rate=sample_rate)
    sf.write(out_path, audio, sample_rate)
    return out_path

def fetch_wikipedia_summary(landmark):
    page = wiki.page(landmark)
    return clean_text(page.summary) if page.exists() else "Found error!"

def recognize_landmark_clip(image):
    if not isinstance(image, Image.Image):
        image = Image.fromarray(image)
    img_in = clip_processor(images=image, return_tensors="pt")
    with torch.no_grad():
        img_embed = clip_model.get_image_features(**img_in)
        img_embed = img_embed / img_embed.norm(p=2, dim=-1, keepdim=True)
    sim = (img_embed @ text_embeds.T).squeeze(0)
    best_idx = sim.argmax().item()
    return landmark_titles[best_idx], sim[best_idx].item()

def process_landmark(landmark):
    txt = fetch_wikipedia_summary(landmark)
    if txt == "Found error!":
        return None
    sm = summarizer(txt, min_length=20, max_length=210)[0]["summary_text"]
    tr = translator(sm, max_length=1000)[0]["translation_text"]
    return text_to_speech(tr)

def process_image_clip(image):
    recognized, score = recognize_landmark_clip(image)
    return process_landmark(recognized)

def process_text_clip(landmark):
    return process_landmark(landmark)

with gr.Blocks() as demo:
    gr.Markdown("## Помощь туристу")
    with gr.Tabs():
        with gr.Tab("CLIP + Sum + Translate + T2S"):
            with gr.Row():
                image_input = gr.Image(label="Загрузите фото", type="pil")
                text_input = gr.Textbox(label="Или введите название")
            audio_output = gr.Audio(label="Результат")
            with gr.Row():
                btn_img = gr.Button("Распознать и перевести")
                btn_txt = gr.Button("Поиск по названию")
            btn_img.click(fn=process_image_clip, inputs=image_input, outputs=audio_output)
            btn_txt.click(fn=process_text_clip, inputs=text_input, outputs=audio_output)

demo.launch(debug=True)