Пользователь
0,0
рейтинг
3 марта 2012 в 15:56

Разработка → Автопереводчик через Skype из песочницы

.NET*
С чего всё начиналось

После того, как я получил сообщение от знакомого с просьбой перевести элементарное предложение с русского на английский и знакомый был послан в Гугл.Переводчик, я подумал, что надо бы это дело автоматизировать, но, так как посылать людей не хорошо, я решил забирать информацию с Гугла и отправлять её через Skype. Но это было бы слишком скучно, и я решил, чтобы бот также звонил и говорил на искомом языке заданное предложение.

Немного поисков, и я нашёл библиотеку Skype4COM, которая позволяет взаимодействовать со Skype.

Также, для работы со звуком, я буду использовать NAudio, а чтобы забирать данные с Гугла — xNet.

Начинаем кодить

Для начала, нам необходимо зарегистрировать Skype4COM в системе, для этого выполним

regsvr32 "Путь_к_dll"

После этого мы можем добавить ссылки на все dll в проект.

Теперь нам необходимо присоедениться к Skype:

  //Проверяем, запущен ли Skype
            if (!Skype.Client.IsRunning)
            {
                Skype.Client.Start(true, true);
            }

            //Подписываемся на событие присоединения к Skype
            ((_ISkypeEvents_Event)Skype).AttachmentStatus += OurAttachmentStatus;

            //Подписываемся на события приёма сообщения и звонка
            Skype.CallStatus += CallStatusChanged;
            Skype.MessageStatus += ReceiveMessage;

            //Присоединяемся к Skype
            Skype.Attach(8);


Так как присоединение к Skype может длиться относительно продолжительное время, нам необходимо узнать, когда мы к нему присоединились:

private void OurAttachmentStatus(TAttachmentStatus status)
        {
            if (status == TAttachmentStatus.apiAttachSuccess)
                textBox1.Text += "Присоединение прошло успешно";
        }


Теперь, когда мы присоединились к Skype, мы можем начать получать сообщения.

private void ReceiveMessage(ChatMessage pmessage, TChatMessageStatus status)
        {
            //Если сообщение получено
            if (status == TChatMessageStatus.cmsReceived)
            {
                //И сообщение имеет формат Искомый язык:Сообщение
                string[] message = pmessage.Body.Split(':');

                if (message.Length != 2)
                    return;

                string mess = message[1];
                string toLang = message[0];

                //Получаем перевод сообщения
                string translate = GetTranslate(mess, toLang);

                //Получаем озвучку данного сообщения
                byte[] bytes = GetFile(translate, toLang);

                Stream stream = new MemoryStream(bytes);
                //Так как Skype4COM может работать только с wav файлами, перекодируем mp3 в wav.
                TimeSpan time = Mp3ToWav(stream, @"d:\test.wav");

                //Звоним абоненту
                Skype.PlaceCall(pmessage.FromHandle);

                //И отправляем данному абоненту перевод
                pmessage.Chat.SendMessage(translate);

                //Задаём, через какое время нужно повесить трубку
                timer = new Timer(time.TotalMilliseconds);
                timer.Elapsed += FinishCall;
                timer.AutoReset = false;
            }
        }


Для работы с сетью, я решил использовать библиотеку xNet, которая позволяет легко автоматизировать действия с веб-сайтами.

Для начала, нам необходимо получить перевод предложения:

 private string GetTranslate(string message, string toLang)
        {
            //Создаём необходимые объекты для работы с веб-запросами
            using (HttpRequest request = new HttpRequest())
            {
                StringDictionary reqParams = new StringDictionary();

                //Можно определять язык, на котором пользователь написал своё сообщение, 
                //но так как моим знакомым врятли понадобиться переводить с китайского, то я решил немного схалтурить
                string myLang;
                if (toLang == "en")
                {
                    myLang = "ru";
                }
                else
                {
                    myLang = "en";
                }

                //Задаём необходимые параметры веб-запроса
                request.UserAgent = HttpHelper.RandomChromeUserAgent();

                reqParams["text"] = message;
                reqParams["tl"] = toLang;
                reqParams["sl"] = myLang;
                reqParams["client"] = "x";

                //Получаем ответ от сервера
                string s = request.Get(
                    "http://translate.google.ru/translate_a/t", reqParams).ToText();

                //Выдираем из него перевод
                string translate = s.Substring(":\"", "\"");

                return translate;
            }
			
        }


Теперь мы можем получить его озвучку:

private byte[] GetFile(string translate, string toLang)
        {
           using (HttpRequest request = new HttpRequest())
            {
                StringDictionary reqParams = new StringDictionary();

                //Задаём необходимые параметры веб-запроса
                reqParams["ie"] = "UTF-8";
                reqParams["q"] = translate;
                reqParams["tl"] = toLang;
                reqParams["prev"] = "input";

                //Получаем файл
                byte[] bytes = request.Get(
                      "http://translate.google.ru/translate_tts", reqParams).ToBytes();

                return bytes;
            }
        }


Теперь нам необходимо перекодировать файл в Wav и сохранить его на диске:

public static TimeSpan Mp3ToWav(Stream mp3File, string outputFile)
        {
            //Создаём объект для чтения mp3 файла
            using (Mp3FileReader reader = new Mp3FileReader(mp3File))
            {
                //Задаём формат выходного файла
                var newFormat = new WaveFormat(16000, 16, 1);
                using (WaveStream pcmStream = new WaveFormatConversionStream(newFormat, reader))
                {
                    //Записываем перекодированный поток в файл
                    WaveFileWriter.CreateWaveFile(outputFile, pcmStream);

                    //Возвращаем продолжительность файла
                    return reader.TotalTime;
                }
            }
        }


Теперь мы можем написать код проигрывания файла:

 private void CallStatusChanged(Call pcall, TCallStatus status)
        {
            //Сохраняем ссылку на звонок, чтобы затем повесить трубку
            call = pcall;

            //Если соединение произошло
            if (status == TCallStatus.clsInProgress)
            {
                //Начинаем проигрывание файла
                pcall.set_InputDevice(TCallIoDeviceType.callIoDeviceTypeFile, @"d:\test.wav");

                //И запускаем таймер
                timer.Start();
            }

        }


Ну и код завершения звонка:
 private void FinishCall(object sender, ElapsedEventArgs e)
        {
            call.Finish();
        }


Файлы проекта

В завершение

Я знаю, что наверняка допустил много ошибок в коде, и надеюсь, что гуру программирования меня просветят, в чём же я не прав.

P.S. По-хорошему, заканчивать звонок надо бы было в событии Skype.CallInputStatusChanged, но сколько я не возился, оно у меня так ни разу и не вызвалось.
Михаил Королёв @RoboNET
карма
10,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (11)

  • 0
    Можно ли как-то делать Skype.Attach(); если запущено несколько скайпов, к конкретному экземпляру?

    Сейчас оно присоединяется к тому который был запущен последним, и елси запустить еще одну копию после того как присоединение уже было выполнено, произойдет переключение.
    • 0
      Насколько я знаю, нельзя. Также я читал на оффициальном форуме Skype, что они не рекомендую запускать несколько экземпляров программы одновременно.
      • 0
        А можно ли через skype4com получить список участников конференции у которых поднята трубка, при этом будучи хостом конференции? В skype4py это работает только в случае если ты не хост конференции.
        • 0
          К сожалению, я не занимался этим вопросом, но скорее всего это работает аналогично с skype4py.
    • 0
      Нельзя так. Вообще библиотика очень странно работает когда два скайпа запущено.
    • 0
      Если эта проблема критична не пользоваться COM оболочкой. На самом деле это оболочка над низкоуровневым api. Оно позволяет с несколькоми скайпами и много че. developer.skype.com/public-api-reference
  • +2
    Как раз сейчас делаю интеграцию скайпа в своем проекте, поэтому заметил пару вещей.

    — В методе Attach() лучше указывать второй параметр False. Метод с True или без параметра будет ждать 30 секунд и если не было ответа, то автоматически зафейлит аттач. Плюс метод вызывается синхронно, т.е. уи подвисает. Делаете Skype.Attach(9, false) и потом в обработчике AttachmentStatus уже получаете ответ, когда юзер нажмет «разрешить».
    — По какой-то, непонятной мне, причине ивент CallStatus иногда вызывается несколько раз с одним и тем же статусом. Такое было мной замечано у статуса Ringing и Finished. Поэтому рекомендую сделать флаг, чтобы не обрабатывать тот же ивент второй раз.
    — Skype.PlaceCall(pmessage.FromHandle); этот метод вылетить с эксепшеном если в данный момент вы уже имеете активный звонок. Советую сначала проверить Skype.ActiveCalls.
  • 0
    а есть тоже самое, но для текста? чтоб можно выделить текст в сообщении и был показан его перевод, и можно было набрать текст на русском, а он бы перевел на английский.
    • +1
      В смысле? Если вы хотите, чтобы в момент отправки сообщения, когда вы написали текст на русском, а он отправился на английском? Это можно легко реализовать, немного переделав код из статьи.
  • 0
    Для не-программистов есть clownfish-translator.com/
  • 0
    регистрировать в системе COM длл-ку вовсе не обязательно… есть COM-free методики.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.