import os
import time
import servicemanager
import win32service
import win32serviceutil
import win32event
from datetime import datetime
class HourlyFileService(win32serviceutil.ServiceFramework):
_svc_name_ = "HourlyFileService"
_svc_display_name_ = "Hourly File Creation Service"
_svc_description_ = "Tworzy nowy plik z timestampem co godzinę."
def __init__(self, args):
super().__init__(args)
# Zdarzenie do obsługi zatrzymania usługi:
self.stop_event = win32event.CreateEvent(None, 0, 0, None)
self.running = True
def SvcStop(self):
# Wywoływane przy zatrzymaniu usługi
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
self.running = False
win32event.SetEvent(self.stop_event)
def SvcDoRun(self):
servicemanager.LogInfoMsg("HourlyFileService – start")
# Pętla główna usługi
while self.running:
try:
# Tworzenie pliku w katalogu usługi
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"plik_{timestamp}.txt"
# np. w katalogu C:\HourlyFiles
output_dir = r"C:\Notes\HourlyFiles"
os.makedirs(output_dir, exist_ok=True)
full_path = os.path.join(output_dir, filename)
with open(full_path, "w") as f:
f.write(f"Plik utworzony: {timestamp}\n")
servicemanager.LogInfoMsg(f"Utworzono plik: {full_path}")
except Exception as e:
servicemanager.LogErrorMsg(f"Błąd przy tworzeniu pliku: {e}")
# Czekaj do 3600 sekund lub do sygnału zatrzymania
win32event.WaitForSingleObject(self.stop_event, 3600 * 1000)
servicemanager.LogInfoMsg("HourlyFileService – stop")
if __name__ == '__main__':
win32serviceutil.HandleCommandLine(HourlyFileService)
python HourlyFileService.py install
python HourlyFileService.py start
python HourlyFileService.py stop
python HourlyFileService.py remove
import os
from googleapiclient.discovery import build
def get_channel_id(api_key, url):
youtube = build('youtube', 'v3', developerKey=api_key)
if 'youtube.com/watch' in url:
# Wyciągnij videoId z URL-a
video_id = url.split('v=')[1].split('&')[0]
response = youtube.videos().list(part='snippet', id=video_id).execute()
return response['items'][0]['snippet']['channelId']
elif 'youtube.com/channel/' in url:
# Bezpośredni ID kanału
return url.rstrip('/').split('/')[-1]
elif ('youtube.com/c/' in url) or ('youtube.com/user/' in url) or ('youtube.com/@' in url):
# Wyciągnij nazwę kanału i znajdź ID przez wyszukiwanie
channel_name = url.rstrip('/').split('/')[-1]
response = youtube.search().list(
part='id',
q=channel_name,
type='channel',
maxResults=1
).execute()
return response['items'][0]['id']['channelId']
else:
raise ValueError("Nieprawidłowy URL YouTube")
# Example usage
api_key = os.getenv('YOUTUBE_API_KEY') # Replace with your actual API key
url = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' # Example video URL
channel_id = get_channel_id(api_key, url)
print(f"Channel ID: {channel_id}")
try
{
var youtubeService = new YTHelperService();
string youtubeUrl = "https://www.youtube.com/@nickchapsas";
Console.WriteLine($"Pobieranie Channel ID dla: {youtubeUrl}");
// Krok 1: Znajdź Channel ID na podstawie URL
string channelId = await youtubeService.GetChannelIdFromUrl(youtubeUrl);
if (string.IsNullOrEmpty(channelId))
{
Console.WriteLine("Nie udało się znaleźć Channel ID");
return;
}
Console.WriteLine($"Znaleziony Channel ID: {channelId}");
Console.WriteLine();
// Krok 2: Pobierz ostatnie filmiki
Console.WriteLine("Pobieranie ostatnich filmików...");
var videos = await youtubeService.GetLatestVideos(channelId);
// Krok 3: Wyświetl listę filmików
youtubeService.DisplayVideos(videos);
}
catch (Exception ex)
{
Console.WriteLine($"Wystąpił błąd: {ex.Message}");
}
Console.WriteLine("\nNaciśnij dowolny klawisz aby zakończyć...");
Console.ReadKey();
/// <summary>
/// Model reprezentujący informacje o filmiku YouTube
/// </summary>
public class VideoInfo
{
/// <summary>
/// Unikalny identyfikator filmu
/// </summary>
public string Id { get; set; }
/// <summary>
/// Pełny URL do filmu
/// </summary>
public string Url { get; set; }
/// <summary>
/// Tytuł filmu
/// </summary>
public string Title { get; set; }
/// <summary>
/// Opis filmu
/// </summary>
public string Description { get; set; }
/// <summary>
/// Data publikacji filmu
/// </summary>
public string PublishedAt { get; set; }
/// <summary>
/// Nazwa kanału
/// </summary>
public string ChannelTitle { get; set; }
}
/// <summary>
/// Klasa do zarządzania konfiguracją aplikacji
/// </summary>
public class ConfigurationManager
{
private readonly IConfiguration _configuration;
public ConfigurationManager()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
_configuration = builder.Build();
}
/// <summary>
/// Klucz API YouTube
/// </summary>
public string ApiKey => Environment.GetEnvironmentVariable("YOUTUBE_API_KEY");
/// <summary>
/// Nazwa aplikacji
/// </summary>
public string ApplicationName => GetConfigValue("YouTube:ApplicationName");
/// <summary>
/// Maksymalna liczba wyników do pobrania
/// </summary>
public long MaxResults => long.Parse(GetConfigValue("YouTube:MaxResults", "10"));
/// <summary>
/// Pobierz wartość z konfiguracji
/// </summary>
/// <param name="key">Klucz konfiguracji</param>
/// <param name="defaultValue">Wartość domyślna</param>
/// <returns>Wartość konfiguracji</returns>
private string GetConfigValue(string key, string defaultValue = null)
{
var value = _configuration[key];
if (string.IsNullOrEmpty(value))
{
if (defaultValue != null)
return defaultValue;
throw new InvalidOperationException($"Brak konfiguracji dla klucza: {key}");
}
return value;
}
}
public class YTHelperService
{
private readonly YouTubeService _youtubeService;
private readonly ConfigurationManager _config;
public YTHelperService()
{
_config = new ConfigurationManager();
_youtubeService = new YouTubeService(new BaseClientService.Initializer() { ApiKey = _config.ApiKey });
}
/// <summary>
/// Znajdź Channel ID na podstawie URL YouTube
/// </summary>
public async Task<string> GetChannelIdFromUrl(string url)
{
try
{
// Sposób 1: Sprawdź czy to bezpośredni link do kanału z ID
var channelIdMatch = Regex.Match(url, @"youtube\.com/channel/([a-zA-Z0-9_-]+)");
if (channelIdMatch.Success)
{
return channelIdMatch.Groups[1].Value;
}
// Sposób 2: Sprawdź czy to link do filmu - wyciągnij Channel ID z filmu
var videoIdMatch = Regex.Match(url, @"(?:youtube\.com/watch\?v=|youtu\.be/)([a-zA-Z0-9_-]+)");
if (videoIdMatch.Success)
{
string videoId = videoIdMatch.Groups[1].Value;
return await GetChannelIdFromVideoId(videoId);
}
// Sposób 3: Sprawdź czy to link z nazwą użytkownika (@username lub /user/)
var usernameMatch = Regex.Match(url, @"youtube\.com/@([a-zA-Z0-9_-]+)");
if (usernameMatch.Success)
{
string username = usernameMatch.Groups[1].Value;
return await GetChannelIdByUsername(username);
}
var userMatch = Regex.Match(url, @"youtube\.com/user/([a-zA-Z0-9_-]+)");
if (userMatch.Success)
{
string username = userMatch.Groups[1].Value;
return await GetChannelIdByUsername(username);
}
// Sposób 4: Sprawdź czy to link z /c/
var customMatch = Regex.Match(url, @"youtube\.com/c/([a-zA-Z0-9_-]+)");
if (customMatch.Success)
{
string customName = customMatch.Groups[1].Value;
return await GetChannelIdByUsername(customName);
}
throw new ArgumentException("Nierozpoznany format URL YouTube");
}
catch (Exception ex)
{
Console.WriteLine($"Błąd podczas pobierania Channel ID: {ex.Message}");
return null;
}
}
/// <summary>
/// Pobierz Channel ID na podstawie Video ID
/// </summary>
private async Task<string> GetChannelIdFromVideoId(string videoId)
{
var videoRequest = _youtubeService.Videos.List("snippet");
videoRequest.Id = videoId;
var videoResponse = await videoRequest.ExecuteAsync();
var video = videoResponse.Items.FirstOrDefault();
return video?.Snippet.ChannelId;
}
/// <summary>
/// Pobierz Channel ID na podstawie nazwy użytkownika (wyszukiwanie)
/// </summary>
private async Task<string> GetChannelIdByUsername(string username)
{
var searchRequest = _youtubeService.Search.List("snippet");
searchRequest.Q = username;
searchRequest.Type = "channel";
searchRequest.MaxResults = 1;
var searchResponse = await searchRequest.ExecuteAsync();
var channel = searchResponse.Items.FirstOrDefault();
return channel?.Snippet.ChannelId;
}
/// <summary>
/// Pobierz ostatnie filmiki z danego kanału
/// </summary>
public async Task<List<VideoInfo>> GetLatestVideos(string channelId)
{
try
{
var searchRequest = _youtubeService.Search.List("snippet");
searchRequest.ChannelId = channelId;
searchRequest.Type = "video";
searchRequest.Order = SearchResource.ListRequest.OrderEnum.Date;
searchRequest.MaxResults = _config.MaxResults;
var searchResponse = await searchRequest.ExecuteAsync();
var videos = new List<VideoInfo>();
foreach (var item in searchResponse.Items)
{
var video = new VideoInfo
{
Id = item.Id.VideoId,
Url = $"https://www.youtube.com/watch?v={item.Id.VideoId}",
Title = item.Snippet.Title,
Description = item.Snippet.Description,
PublishedAt = item.Snippet.PublishedAt?.ToString("dd.MM.yyyy HH:mm") ?? "Brak daty",
ChannelTitle = item.Snippet.ChannelTitle
};
videos.Add(video);
}
return videos;
}
catch (Exception ex)
{
Console.WriteLine($"Błąd podczas pobierania filmików: {ex.Message}");
return new List<VideoInfo>();
}
}
/// <summary>
/// Wyświetl listę filmików w konsoli
/// </summary>
public void DisplayVideos(List<VideoInfo> videos)
{
if (videos == null || !videos.Any())
{
Console.WriteLine("Brak filmików do wyświetlenia.");
return;
}
Console.WriteLine($"Znaleziono {videos.Count} filmików:\n");
Console.WriteLine(new string('=', 100));
for (int i = 0; i < videos.Count; i++)
{
var video = videos[i];
Console.WriteLine($"#{i + 1}");
Console.WriteLine($"Tytuł: {video.Title}");
Console.WriteLine($"URL: {video.Url}");
Console.WriteLine($"ID: {video.Id}");
Console.WriteLine($"Kanał: {video.ChannelTitle}");
Console.WriteLine($"Data publikacji: {video.PublishedAt}");
Console.WriteLine($"Opis: {TruncateDescription(video.Description)}");
Console.WriteLine(new string('-', 100));
}
}
/// <summary>
/// Skróć opis do określonej długości
/// </summary>
private string TruncateDescription(string description)
{
if (string.IsNullOrEmpty(description))
return "Brak opisu";
const int maxLength = 200;
if (description.Length <= maxLength)
return description;
return description.Substring(0, maxLength) + "...";
}
}
#from pytube import YouTube
from pytubefix import YouTube
from pytubefix.cli import on_progress
# Replace with your desired YouTube video URL
video_url = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
try:
yt = YouTube(video_url,on_progress_callback = on_progress)
# Get the highest resolution progressive stream (video + audio)
stream = yt.streams.get_highest_resolution()
print(f"Downloading: {yt.title}")
stream.download(output_path= r"C:\Notes\PyYouTubeTest")
print("Download complete!")
except Exception as e:
print("Error:", e)
import yt_dlp
# Replace with your desired YouTube URL
video_url = 'https://www.youtube.com/watch?v=2lAe1cqCOXo'
# Download options
ydl_opts = {
'format': 'bestvideo+bestaudio/best',
'outtmpl': r"C:\Notes\ytDlpTest\%(title)s.%(ext)s",
'noplaylist': True, # Don't download playlists
'quiet': False, # Show progress
'no_warnings': True, # Suppress warnings
'ignoreerrors': True, # Skip videos that cause errors
}
# Downloading the video
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download([video_url])
import yt_dlp
def download_as_mp3(url, output_path='.'):
ydl_opts = {
'format': 'bestaudio/best',
'outtmpl': r"C:\Notes\ytDlpTest\%(title)s.%(ext)s",
'postprocessors': [
{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
'preferredquality': '192',
},
{
'key': 'FFmpegMetadata',
}
],
'quiet': False,
'no_warnings': True, # Suppress warnings
'ignoreerrors': True, # Skip videos that cause errors
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download([url])
# Przykładowe użycie:
download_as_mp3('https://www.youtube.com/watch?v=dQw4w9WgXcQ')
using YoutubeDLSharp;
var ytdl = new YoutubeDL();
// set the path of yt-dlp and FFmpeg if they're not in PATH or current directory
ytdl.YoutubeDLPath = "C:\\MyScripts\\yt-dlp.exe";
ytdl.FFmpegPath = "C:\\MyScripts\\yt-dlg\\ffmpeg.exe";
// optional: set a different download folder
ytdl.OutputFolder = @"C:\Notes\ytDlpTestCSHARP";
// download a video
var res = await ytdl
.RunVideoDownload("https://www.youtube.com/watch?v=bq9ghmgqoyc");
// the path of the downloaded file
string path = res.Data;
Console.WriteLine($"Pobrano plik: {path}");
using System.Diagnostics;
string youtubeUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ";
string ytDlpPath = @"C:\MyScripts\yt-dlp.exe";
string ffmpegPath = @"C:\MyScripts\yt-dlg\ffmpeg.exe";
string outputFolder = @"C:\Notes\ytDlpTestCSHARP";
DownloadYouTubeAsMp3(youtubeUrl, ytDlpPath, ffmpegPath, outputFolder);
static void DownloadYouTubeAsMp3(string url, string ytDlpPath, string ffmpegPath, string outputFolder)
{
if (!Directory.Exists(outputFolder))
{
Directory.CreateDirectory(outputFolder);
}
// Komenda yt-dlp do pobierania i konwersji na mp3
string arguments = $"--ffmpeg-location \"{ffmpegPath}\" -x --audio-format mp3 -o \"{outputFolder}\\%(title)s.%(ext)s\" \"{url}\"";
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = ytDlpPath,
Arguments = arguments,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
using (Process process = new Process())
{
process.StartInfo = psi;
process.OutputDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
Console.WriteLine("[yt-dlp] " + e.Data);
};
process.ErrorDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
Console.WriteLine("[error] " + e.Data);
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
}
Console.WriteLine("Pobieranie zakończone.");
}
using YoutubeExplode;
using YoutubeExplode.Converter;
string videoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ";
string outputFolder = @"C:\Notes\ytDlpTestCSHARP";
if (!Directory.Exists(outputFolder))
Directory.CreateDirectory(outputFolder);
string outputPath = Path.Combine(outputFolder, "output.mp3");
var youtube = new YoutubeClient();
await youtube.Videos.DownloadAsync(
videoUrl,
outputPath,
builder => builder.SetContainer("mp3") // <- MP3!
);
Console.WriteLine($"Plik zapisany: {outputPath}");
import whisper
def transcribe_video(video_path, output_path):
try:
model = whisper.load_model("base")
result = model.transcribe(video_path)
if not result or "text" not in result:
print("Transkrypcja nie powiodła się.")
return
with open(output_path, "w", encoding="utf-8") as f:
f.write(result["text"])
print("Transkrypcja została zapisana pomyślnie.")
except Exception as e:
print(f"Wystąpił błąd: {e}")
if __name__ == "__main__":
video_path = r"D:\WDI_GRAPHQL.mp4"
output_path = r"D:\WDI_GRAPHQL.txt"
transcribe_video(video_path, output_path)
result = model.transcribe(video_path, fp16=False)
import whisper
import threading
import time
def transcribe_video(video_path, output_path):
try:
# Załaduj model Whisper
model = whisper.load_model("base")
# Przygotuj mechanizm odliczania czasu
stop_event = threading.Event()
def timer():
start_time = time.time()
animation = "|/-\\"
idx = 0
while not stop_event.is_set():
elapsed = int(time.time() - start_time)
print(f"\rCzas trwania transkrypcji: {elapsed} sek {animation[idx % len(animation)]}", end="", flush=True)
idx += 1
time.sleep(1)
# na koniec nowe wiersz, żeby nie zostać na tej samej linii
print()
# Uruchom timer w tle
t = threading.Thread(target=timer)
t.start()
# Wykonaj transkrypcję (to może chwilę potrwać)
result = model.transcribe(video_path, fp16=False)
# Zatrzymaj timer
stop_event.set()
t.join()
# Sprawdź, czy transkrypcja się powiodła
if not result or "text" not in result:
print("Transkrypcja nie powiodła się.")
return
# Zapisz transkrypcję do pliku
with open(output_path, "w", encoding="utf-8") as f:
f.write(result["text"])
print("Transkrypcja została zapisana pomyślnie.")
except Exception as e:
print(f"Wystąpił błąd: {e}")
if __name__ == "__main__":
video_path = r"D:\WDI_KAFKA.mp4"
output_path = r"D:\WDI_KAFKA.txt"
transcribe_video(video_path, output_path)
import whisper
from datetime import timedelta
def format_timestamp(seconds):
td = timedelta(seconds=seconds)
return str(td)[:-3].replace('.', ',')
def transcribe_to_srt(audio_path, srt_path):
model = whisper.load_model("base")
# Możesz użyć "small", "medium" lub "large" dla lepszej dokładności
result = model.transcribe(audio_path)
segments = result['segments']
with open(srt_path, 'w', encoding='utf-8') as srt_file:
for i, segment in enumerate(segments, start=1):
start = format_timestamp(segment['start'])
end = format_timestamp(segment['end'])
text = segment['text'].strip()
srt_file.write(f"{i}\n{start} --> {end}\n{text}\n\n")
# Przykład użycia
transcribe_to_srt(r"D:\WDI_GRAPHQL.mp4",r"D:\WDI_GRAPHQL.SRT")
23
0:00:51,600 --> 0:00:54,440
Po raz na jakiś czas patrzę, co się zmienia w grawkulel
24
0:00:54,440 --> 0:00:56,320
i jeszcze to w kontekście Cisharpa i Dukneta.
25
0:00:56,320 --> 0:00:57,040
Dokładnie.
26
0:00:57,520 --> 0:00:59,320
I tak w ten sposób powstała ta prekcja.
27
0:00:59,320 --> 0:01:02,520
No po, mamy mnóstwo zakręć, mnóstwo sposobów,
try
{
await transcriber.InitializeAsync("C:\\MyScripts\\whsipermodel\\ggml-base.bin");
// Transcribe MP3 file
string mp3Result = await transcriber.TranscribeAudioFileAsync
("D:\\Cool Kids of death - 009 -piosenki o milosci.mp3");
Console.WriteLine("MP3 MUSIC Transcription:");
Console.WriteLine(mp3Result);
// Transcribe MP3 file
string mp3Result2 = await transcriber.TranscribeAudioFileAsync
(@"D:\Game Boy Advance: Incredible tech on just 2 AA Batteries.mp3");
Console.WriteLine("MP3 Transcription:");
Console.WriteLine(mp3Result2);
// Transcribe video file (audio track will be extracted)
string videoResult = await transcriber.TranscribeAudioFileAsync
("D:\\AQP71Kcw8e.mp4");
Console.WriteLine("\nVideo Transcription:");
Console.WriteLine(videoResult);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
finally
{
transcriber.Dispose();
}
public class WhisperTranscriber
{
private WhisperFactory whisperFactory;
private WhisperProcessor processor;
public async Task InitializeAsync(string modelPath = null)
{
// If no model path provided, download and use a default model
if (string.IsNullOrEmpty(modelPath))
{
modelPath = await DownloadModelAsync();
}
// Verify model file exists
if (!File.Exists(modelPath))
{
throw new FileNotFoundException($"Whisper model file not found: {modelPath}");
}
// Create Whisper factory and processor
whisperFactory = WhisperFactory.FromPath(modelPath);
processor = whisperFactory.CreateBuilder()
.WithLanguage("auto")
.Build();
}
public async Task<string> TranscribeAudioFileAsync(string inputFilePath)
{
try
{
string wavFilePath = null;
if (FFmpegConvertToWave.IsFFmpegAvailable() == true)
{
wavFilePath = NAudioConvertToWave.ConvertToWav(inputFilePath);
}
else
{
// Convert to 16kHz mono WAV using FFmpeg
wavFilePath = await FFmpegConvertToWave.ConvertToWhisperFormatAsync(inputFilePath);
}
// Transcribe audio
var fullTranscription = "";
using (var fileStream = File.OpenRead(wavFilePath))
{
await foreach (var segment in processor.ProcessAsync(fileStream))
{
Console.WriteLine($"[{segment.Start:hh\\:mm\\:ss} -> {segment.End:hh\\:mm\\:ss}] {segment.Text}");
fullTranscription += segment.Text + " ";
}
}
// Clean up temporary file if it was created
if (wavFilePath != inputFilePath && File.Exists(wavFilePath))
{
File.Delete(wavFilePath);
}
return fullTranscription.Trim();
}
catch (Exception ex)
{
throw new Exception($"Error transcribing audio: {ex.Message}", ex);
}
}
private async Task<string> DownloadModelAsync()
{
var modelDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "WhisperModels");
Directory.CreateDirectory(modelDir);
var modelPath = Path.Combine(modelDir, "ggml-base.bin");
if (!File.Exists(modelPath))
{
Console.WriteLine("Downloading Whisper base model...");
using var client = new HttpClient();
var url = "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.bin";
try
{
var response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
await using var fileStream = File.Create(modelPath);
await response.Content.CopyToAsync(fileStream);
Console.WriteLine($"Model downloaded to: {modelPath}");
}
catch (Exception ex)
{
throw new Exception($"Failed to download model: {ex.Message}");
}
}
return modelPath;
}
public void Dispose()
{
processor?.Dispose();
whisperFactory?.Dispose();
}
}
using System.Diagnostics;
namespace ConsoleAppWhispernet;
public static class FFmpegConvertToWave
{
public static async Task<string> ConvertToWhisperFormatAsync(string inputFilePath)
{
if (!IsFFmpegAvailable())
{
throw new Exception
("FFmpeg is not available. Please install FFmpeg or add it to your PATH.");
}
var outputPath = Path.ChangeExtension(inputFilePath, "_whisper.wav");
var arguments =
$"-i \"{inputFilePath}\" -ar 16000 -ac 1 -c:a pcm_s16le \"{outputPath}\" -y";
var processInfo = new ProcessStartInfo
{
FileName = "ffmpeg",
Arguments = arguments,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
try
{
using var process = Process.Start(processInfo);
if (process == null)
{
throw new Exception("Failed to start FFmpeg process.");
}
var errorTask = process.StandardError.ReadToEndAsync();
var outputTask = process.StandardOutput.ReadToEndAsync();
await process.WaitForExitAsync();
var errorOutput = await errorTask;
var stdOutput = await outputTask;
if (process.ExitCode != 0)
{
throw
new Exception($"FFmpeg conversion failed (Exit Code {process.ExitCode}): {errorOutput}");
}
if (!File.Exists(outputPath))
{
throw
new Exception("FFmpeg conversion completed but output file was not created.");
}
return outputPath;
}
catch (Exception ex)
{
throw new Exception($"Error running FFmpeg: {ex.Message}", ex);
}
}
public static bool IsFFmpegAvailable()
{
try
{
var processInfo = new ProcessStartInfo
{
FileName = "ffmpeg",
Arguments = "-version",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var process = Process.Start(processInfo);
process.WaitForExit();
return process.ExitCode == 0;
}
catch
{
return false;
}
}
}
using NAudio.Wave;
using NAudio.Wave.SampleProviders;
namespace ConsoleAppWhispernet;
public static class NAudioConvertToWave
{
public static string ConvertToWav(string inputFilePath)
{
var extension = Path.GetExtension(inputFilePath).ToLower();
var outputPath = Path.ChangeExtension(inputFilePath, "_16khz.wav");
try
{
using (var reader = CreateAudioReader(inputFilePath, extension))
{
var sampleProvider = ConvertTo16KhzMono
(reader, out IDisposable resamplerToDispose);
using (resamplerToDispose)
{
var waveProvider = new SampleToWaveProvider16(sampleProvider);
WaveFileWriter.CreateWaveFile(outputPath, waveProvider);
}
}
return outputPath;
}
catch (Exception ex)
{
throw new Exception($"Error converting {inputFilePath} to 16kHz WAV: {ex.Message}", ex);
}
}
private static AudioFileReader CreateAudioReader
(string inputFilePath, string extension)
{
if (extension == ".mp3" || extension == ".wav")
{
return new AudioFileReader(inputFilePath);
}
else
{
// Convert unsupported formats using MediaFoundationReader to a WAV file in memory
var tempReader = new MediaFoundationReader(inputFilePath);
var tempWavPath = Path.GetTempFileName();
WaveFileWriter.CreateWaveFile(tempWavPath, tempReader);
tempReader.Dispose();
return new AudioFileReader(tempWavPath);
}
}
private static ISampleProvider ConvertTo16KhzMono
(AudioFileReader reader, out IDisposable resamplerToDispose)
{
// First, convert to mono if needed
ISampleProvider monoProvider;
if (reader.WaveFormat.Channels == 1)
{
monoProvider = reader; // Already mono
}
else
{
monoProvider = new StereoToMonoSampleProvider(reader);
}
// Then, resample to 16000 Hz if needed
if (reader.WaveFormat.SampleRate != 16000)
{
var resampler = new WdlResamplingSampleProvider(monoProvider, 16000);
resamplerToDispose = reader; // Dispose reader at the end
return resampler;
}
// No resampling needed
resamplerToDispose = reader; // Still need to dispose the reader
return monoProvider;
}
}
#pip install nemo_toolkit[asr] torch torchaudio
from nemo.collections.asr.models import EncDecCTCModel
import torch
# Load pretrained ASR model (Apache 2.0)
asr_model = EncDecCTCModel.from_pretrained("stt_en_conformer_ctc_large")
# Path to the audio file (WAV, 16kHz, mono)
audio_file = "audio.wav"
# Transcribe
transcription = asr_model.transcribe([audio_file])
print("Transcript:", transcription[0])
#In Batch example
audio_files = ["audio1.wav", "audio2.wav", "audio3.wav"]
transcriptions = asr_model.transcribe(audio_files)
for file, text in zip(audio_files, transcriptions):
print(f"{file}: {text}")
#pip install vosk
#pip install soundfile
from vosk import Model, KaldiRecognizer
import wave
import json
# Load model
model = Model("models/vosk-model-small-en-us-0.15")
# Open audio file
wf = wave.open("audio.wav", "rb")
if wf.getnchannels() != 1 or wf.getsampwidth() != 2 or wf.getframerate() != 16000:
raise ValueError("Audio must be WAV format Mono PCM 16kHz.")
# Recognizer
rec = KaldiRecognizer(model, wf.getframerate())
result_text = ""
while True:
data = wf.readframes(4000)
if len(data) == 0:
break
if rec.AcceptWaveform(data):
result = json.loads(rec.Result())
result_text += result.get("text", "") + " "
# Final result
final_result = json.loads(rec.FinalResult())
result_text += final_result.get("text", "")
print("Transcription:", result_text)
using NAudio.Wave;
using Vosk;
const string modelPath = "models/vosk-model-small-en-us-0.15";
const string audioPath = "audio.wav";
// Init Vosk
Vosk.Vosk.SetLogLevel(0);
var model = new Model(modelPath);
// Open WAV
using var waveReader = new WaveFileReader(audioPath);
if (waveReader.WaveFormat.Encoding != WaveFormatEncoding.Pcm ||
waveReader.WaveFormat.SampleRate != 16000 ||
waveReader.WaveFormat.Channels != 1)
{
throw new Exception("Audio file must be 16kHz, mono, PCM WAV.");
}
var recognizer = new VoskRecognizer(model, 16000.0f);
byte[] buffer = new byte[4096];
int bytesRead;
string fullText = "";
while ((bytesRead = waveReader.Read(buffer, 0, buffer.Length)) > 0)
{
if (recognizer.AcceptWaveform(buffer, bytesRead))
{
var result = recognizer.Result();
fullText += ExtractText(result) + " ";
}
}
var final = recognizer.FinalResult();
fullText += ExtractText(final);
Console.WriteLine("Transcription:");
Console.WriteLine(fullText);
static string ExtractText(string json)
{
using var doc = System.Text.Json.JsonDocument.Parse(json);
return doc.RootElement.GetProperty("text").GetString();
}
from youtube_transcript_api import YouTubeTranscriptApi
# ID filmiku YouTube
video_id = '2uLGXe95kTo'
# Pobierz transkrypcję
transcript = YouTubeTranscriptApi.get_transcript(video_id)
# Wyświetl transkrypcję
for line in transcript:
print(line['text'])
using YoutubeExplode;
var videoId = "2uLGXe95kTo";
var youtube = new YoutubeClient();
var trackManifest = await
youtube.Videos.ClosedCaptions.GetManifestAsync(videoId);
// Wybierz ścieżkę w języku angielskim (lub innym, np. "pl" dla polskiego)
var trackInfo =
trackManifest.GetByLanguage("en") ?? trackManifest.Tracks[0];
var captions = await youtube.Videos.ClosedCaptions.GetAsync(trackInfo);
var r = captions.TryGetByTime(new TimeSpan(0, 0, 10));
Console.WriteLine(r.Text);
foreach (var caption in captions.Captions)
{
Console.WriteLine(caption.Text);
}
import ollama
# Inicjalizacja klienta ollama z dokładnym adresem
client = ollama.Client(host='http://localhost:11434')
# Wywołanie modelu z przykładowym promptem
response = client.generate(
model="llama3",
prompt="Dlaczego trawa jest zielona?"
)
# Wyświetlenie odpowiedzi
print(response['response'])
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder();
builder.Services.AddChatClient
(new OllamaChatClient(new Uri("http://localhost:11434"),
"llama3"));
var app = builder.Build();
var chatClient = app.Services.
GetRequiredService<IChatClient>();
var chatHistory = new List<ChatMessage>();
while (true)
{
Console.WriteLine("Enter your prompt:");
var userPrompt = Console.ReadLine();
chatHistory.Add(new ChatMessage(ChatRole.User, userPrompt));
Console.WriteLine("Response from AI:");
var chatResponse = "";
await foreach (var item in
chatClient.CompleteStreamingAsync(chatHistory))
{
// We're streaming the response,
// so we get each message as it arrives
Console.Write(item.Text);
chatResponse += item.Text;
}
chatHistory.Add(new ChatMessage(ChatRole.Assistant,
chatResponse));
Console.WriteLine();
}
using Codeblaze.SemanticKernel.Connectors.Ollama;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
var builder = Kernel.CreateBuilder();
// Register HttpClient for Ollama API communication
builder.Services.AddTransient<HttpClient>();
// Configure Ollama chat completion service
builder.AddOllamaChatCompletion(
modelId: "llama3:8b", // Replace with your desired model
baseUrl: "http://localhost:11434" // Ollama's default endpoint
);
var kernel = builder.Build();
var chatService = kernel.GetRequiredService<IChatCompletionService>();
var history = new ChatHistory();
history.AddUserMessage("What is the capital of Poland?");
var response = await chatService.GetChatMessageContentsAsync(history);
Console.WriteLine(response[^1].Content);
//< PackageReference Include = "Codeblaze.SemanticKernel.Connectors.Ollama" Version = "1.3.1" />
//< PackageReference Include = "Microsoft.SemanticKernel" Version = "1.54.0" />
//<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
import imaplib
import email
from email.header import decode_header
# Dane logowania
username = "twoje@proton.me"
password = "twoje_hasło"
# Łączenie z serwerem IMAP Gmail
mail = imaplib.IMAP4_SSL("imap.gmail.com")
mail.login(username, password)
# Wybór skrzynki odbiorczej
mail.select("inbox")
# Wyszukiwanie wiadomości o określonym temacie i zawierających określone słowa
subject = "temat_wiadomości"
keywords = ["słowo1", "słowo2"]
search_criteria = f'(SUBJECT "{subject}")'
status, messages = mail.search(None, search_criteria)
if status == "OK":
email_ids = messages[0].split()
for email_id in email_ids:
status, msg_data = mail.fetch(email_id, "(RFC822)")
if status == "OK":
raw_email = msg_data[0][1]
email_message = email.message_from_bytes(raw_email)
# Sprawdzanie, czy wiadomość zawiera określone słowa
contains_keywords = all(keyword.lower() in email_message.get_payload().lower() for keyword in keywords)
if contains_keywords:
print(f"Znaleziono wiadomość o ID: {email_id}")
print(f"Temat: {email_message['Subject']}")
print(f"Od: {email_message['From']}")
print(f"Data: {email_message['Date']}")
print("Zawartość:")
print(email_message.get_payload())
print("=" * 50)
# Zamykanie połączenia
mail.close()
mail.logout()
import os.path
import base64
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from bs4 import BeautifulSoup
# If modifying these SCOPES, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
def get_gmail_service():
"""Shows basic usage of the Gmail API.
Lists the user's Gmail labels.
"""
creds = None
# The file token.json stores the user's access and refresh tokens.
if os.path.exists(r'C:\Notes\token.json'):
creds = Credentials.from_authorized_user_file(r'C:\Notes\token.json', SCOPES)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
r'C:\Notes\credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open(r'C:\Notes\token.json', 'w') as token:
token.write(creds.to_json())
service = build('gmail', 'v1', credentials=creds)
return service
def get_latest_emails(service, max_results=10):
results = service.users().messages().list(userId='me', maxResults=max_results).execute()
messages = results.get('messages', [])
return messages
def get_email_details(service, msg_id):
message = service.users().messages().get(userId='me', id=msg_id, format='full').execute()
payload = message.get('payload', {})
headers = payload.get('headers', [])
subject = next(header['value'] for header in headers if header['name'] == 'Subject')
sender = next(header['value'] for header in headers if header['name'] == 'From')
date = next(header['value'] for header in headers if header['name'] == 'Date')
# Extract the body of the email
if 'parts' in payload:
parts = payload['parts']
data = parts[0]['body']['data']
else:
data = payload['body']['data']
data = data.replace("-", "+").replace("_", "/")
decoded_data = base64.b64decode(data)
soup = BeautifulSoup(decoded_data, 'html.parser')
body = soup.get_text()
return {
'subject': subject,
'sender': sender,
'date': date,
'body': body
}
def filter_emails_by_keywords(emails, keywords):
filtered_emails = []
for email in emails:
for keyword in keywords:
if keyword.lower() in email['subject'].lower() or keyword.lower() in email['body'].lower():
filtered_emails.append(email)
break
return filtered_emails
def main():
service = get_gmail_service()
messages = get_latest_emails(service)
emails = []
for message in messages:
email_details = get_email_details(service, message['id'])
emails.append(email_details)
keywords = ['C#', '.NET'] # Add your keywords here
filtered_emails = filter_emails_by_keywords(emails, keywords)
for email in filtered_emails:
print(f"Subject: {email['subject']}")
print(f"From: {email['sender']}")
print(f"Date: {email['date']}")
print(f"Body: {email['body']}")
print("\n")
if __name__ == '__main__':
main()
using Google.Apis.Auth.OAuth2;
using Google.Apis.Gmail.v1;
using Google.Apis.Gmail.v1.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Text;
using System.Text.Json;
var host = CreateHostBuilder(args).Build();
var processor = host.Services.GetRequiredService<EmailProcessor>();
await processor.ProcessEmailsAsync();
static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
})
.ConfigureServices((context, services) =>
{
services.Configure<AppConfig>(context.Configuration.GetSection("AppConfig"));
services.AddScoped<IMyGmailService, MyGmailService>();
services.AddScoped<IDatabaseService, DatabaseService>();
services.AddScoped<IOllamaService, OllamaService>();
services.AddScoped<IFileService, FileService>();
services.AddScoped<EmailProcessor>();
});
public class AppConfig
{
public GmailConfig Gmail { get; set; }
public DatabaseConfig Database { get; set; }
public OllamaConfig Ollama { get; set; }
public FileConfig Files { get; set; }
public FilterConfig Filters { get; set; }
}
public class GmailConfig
{
public string CredentialsPath { get; set; }
public string ApplicationName { get; set; }
public List<string> Scopes { get; set; }
}
public class DatabaseConfig
{
public string ConnectionString { get; set; }
}
public class OllamaConfig
{
public string BaseUrl { get; set; }
public string Model { get; set; }
public string SummarizePrompt { get; set; }
}
public class FileConfig
{
public string OutputDirectory { get; set; }
public string FileNameFormat { get; set; }
}
public class FilterConfig
{
public List<string> SubjectKeywords { get; set; }
public List<string> ContentKeywords { get; set; }
public int MaxEmailsPerRun { get; set; }
}
public class ProcessedEmail
{
public string Id { get; set; }
public string Subject { get; set; }
public string From { get; set; }
public DateTime ReceivedDate { get; set; }
public DateTime ProcessedDate { get; set; }
public string SummaryFilePath { get; set; }
public bool IsProcessed { get; set; }
}
public class EmailContent
{
public string Id { get; set; }
public string Subject { get; set; }
public string From { get; set; }
public string Body { get; set; }
public DateTime ReceivedDate { get; set; }
}
public interface IMyGmailService
{
Task<List<EmailContent>> GetFilteredEmailsAsync(FilterConfig filters);
}
public interface IDatabaseService
{
Task InitializeDatabaseAsync();
Task<bool> IsEmailProcessedAsync(string emailId);
Task MarkEmailAsProcessedAsync(ProcessedEmail email);
Task<List<ProcessedEmail>> GetProcessedEmailsAsync();
}
public interface IOllamaService
{
Task<string> SummarizeEmailAsync(string emailContent);
}
public interface IFileService
{
Task<string> SaveSummaryAsync(EmailContent email, string summary);
}
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AppConfig": {
"Gmail": {
"CredentialsPath": "C:\\Notes\\credentials.json",
"ApplicationName": "Gmail Email Processor",
"Scopes": [
"https://www.googleapis.com/auth/gmail.readonly"
]
},
"Database": {
"ConnectionString": "Data Source=emails.db"
},
"Ollama": {
"BaseUrl": "http://localhost:11434",
"Model": "llama2",
"SummarizePrompt": "Proszę o zwięzłe podsumowanie poniższego emaila w języku polskim. Skoncentruj się na najważniejszych informacjach, działaniach do podjęcia i kluczowych szczegółach. Maksymalnie 3-4 zdania.\n\nEmail:\n{EMAIL_CONTENT}\n\nPodsumowanie:"
},
"Files": {
"OutputDirectory": "summaries",
"FileNameFormat": "{DATE}_{TIME}_{SUBJECT}_{ID}.md"
},
"Filters": {
"SubjectKeywords": [
"faktura",
"płatność",
"zamówienie",
"projekt",
"spotkanie"
],
"ContentKeywords": [
"pilne",
"deadline",
"termin",
"wymagane"
],
"MaxEmailsPerRun": 50
}
}
}
public class MyGmailService : IMyGmailService
{
private readonly AppConfig _config;
private readonly ILogger<GmailService> _logger;
private GmailService _service;
public MyGmailService(IOptions<AppConfig> config, ILogger<GmailService> logger)
{
_config = config.Value;
_logger = logger;
}
private async Task<GmailService> GetServiceAsync()
{
if (_service != null) return _service;
UserCredential credential;
using (var stream = new FileStream(_config.Gmail.CredentialsPath, FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
_config.Gmail.Scopes,
"user",
CancellationToken.None,
new FileDataStore("token.json", true));
}
_service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = _config.Gmail.ApplicationName,
});
return _service;
}
public async Task<List<EmailContent>> GetFilteredEmailsAsync(FilterConfig filters)
{
try
{
var service = await GetServiceAsync();
var emails = new List<EmailContent>();
// Budowanie zapytania wyszukiwania
var query = BuildSearchQuery(filters);
var request = service.Users.Messages.List("me");
request.Q = query;
request.MaxResults = filters.MaxEmailsPerRun;
var response = await request.ExecuteAsync();
if (response.Messages == null)
{
_logger.LogInformation("Nie znaleziono emaili spełniających kryteria");
return emails;
}
foreach (var message in response.Messages)
{
var email = await GetEmailContentAsync(service, message.Id);
if (email != null && IsEmailMatchingFilters(email, filters))
{
emails.Add(email);
}
}
_logger.LogInformation($"Pobrano {emails.Count} emaili spełniających kryteria");
return emails;
}
catch (Exception ex)
{
_logger.LogError(ex, "Błąd podczas pobierania emaili z Gmail");
throw;
}
}
private string BuildSearchQuery(FilterConfig filters)
{
var queryParts = new List<string>();
if (filters.SubjectKeywords?.Any() == true)
{
var subjectQuery = string.Join(" OR ", filters.SubjectKeywords.Select(k => $"subject:{k}"));
queryParts.Add($"({subjectQuery})");
}
// Dodaj więcej kryteriów wyszukiwania jeśli potrzeba
queryParts.Add("is:unread"); // Tylko nieprzeczytane
return string.Join(" ", queryParts);
}
private async Task<EmailContent> GetEmailContentAsync(GmailService service, string messageId)
{
try
{
var request = service.Users.Messages.Get("me", messageId);
var message = await request.ExecuteAsync();
var email = new EmailContent
{
Id = messageId,
Subject = GetHeaderValue(message.Payload.Headers, "Subject") ?? "Brak tematu",
From = GetHeaderValue(message.Payload.Headers, "From") ?? "Nieznany nadawca",
ReceivedDate = DateTimeOffset.FromUnixTimeMilliseconds(message.InternalDate ?? 0).DateTime,
Body = ExtractEmailBody(message.Payload)
};
return email;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Błąd podczas pobierania treści emaila {messageId}");
return null;
}
}
private string GetHeaderValue(IList<MessagePartHeader> headers, string name)
{
return headers?.FirstOrDefault(h => h.Name.Equals(name, StringComparison.OrdinalIgnoreCase))?.Value;
}
private string ExtractEmailBody(MessagePart payload)
{
var body = new StringBuilder();
if (payload.Body?.Data != null)
{
var data = Convert.FromBase64String(payload.Body.Data.Replace('-', '+').Replace('_', '/'));
body.Append(Encoding.UTF8.GetString(data));
}
if (payload.Parts != null)
{
foreach (var part in payload.Parts)
{
body.Append(ExtractEmailBody(part));
}
}
return body.ToString();
}
private bool IsEmailMatchingFilters(EmailContent email, FilterConfig filters)
{
// Sprawdź słowa kluczowe w temacie
if (filters.SubjectKeywords?.Any() == true)
{
var hasSubjectMatch = filters.SubjectKeywords.Any(keyword =>
email.Subject.Contains(keyword, StringComparison.OrdinalIgnoreCase));
if (!hasSubjectMatch) return false;
}
// Sprawdź słowa kluczowe w treści
if (filters.ContentKeywords?.Any() == true)
{
var hasContentMatch = filters.ContentKeywords.Any(keyword =>
email.Body.Contains(keyword, StringComparison.OrdinalIgnoreCase));
if (!hasContentMatch) return false;
}
return true;
}
}
public class EmailProcessor
{
private readonly IMyGmailService _gmailService;
private readonly IDatabaseService _databaseService;
private readonly IOllamaService _ollamaService;
private readonly IFileService _fileService;
private readonly AppConfig _config;
private readonly ILogger<EmailProcessor> _logger;
public EmailProcessor(
IMyGmailService gmailService,
IDatabaseService databaseService,
IOllamaService ollamaService,
IFileService fileService,
IOptions<AppConfig> config,
ILogger<EmailProcessor> logger)
{
_gmailService = gmailService;
_databaseService = databaseService;
_ollamaService = ollamaService;
_fileService = fileService;
_config = config.Value;
_logger = logger;
}
public async Task ProcessEmailsAsync()
{
try
{
_logger.LogInformation("Rozpoczynanie przetwarzania emaili...");
// Inicjalizuj bazę danych
await _databaseService.InitializeDatabaseAsync();
// Pobierz emaile z Gmail
var emails = await _gmailService.GetFilteredEmailsAsync(_config.Filters);
_logger.LogInformation($"Znaleziono {emails.Count} emaili do przetworzenia");
var processedCount = 0;
var skippedCount = 0;
foreach (var email in emails)
{
try
{
// Sprawdź czy email był już przetworzony
if (await _databaseService.IsEmailProcessedAsync(email.Id))
{
_logger.LogInformation($"Email {email.Id} był już przetworzony - pomijam");
skippedCount++;
continue;
}
_logger.LogInformation($"Przetwarzanie emaila: {email.Subject}");
// Wyślij do Ollama w celu podsumowania
var summary = await _ollamaService.SummarizeEmailAsync(
$"Temat: {email.Subject}\nOd: {email.From}\nTreść: {email.Body}");
// Zapisz podsumowanie do pliku
var filePath = await _fileService.SaveSummaryAsync(email, summary);
// Oznacz jako przetworzony w bazie danych
var processedEmail = new ProcessedEmail
{
Id = email.Id,
Subject = email.Subject,
From = email.From,
ReceivedDate = email.ReceivedDate,
ProcessedDate = DateTime.Now,
SummaryFilePath = filePath,
IsProcessed = true
};
await _databaseService.MarkEmailAsProcessedAsync(processedEmail);
processedCount++;
_logger.LogInformation($"Email {email.Id} został pomyślnie przetworzony");
}
catch (Exception ex)
{
_logger.LogError(ex, $"Błąd podczas przetwarzania emaila {email.Id}");
}
}
_logger
.LogInformation
($"Zakończono przetwarzanie emaili. Przetworzono: {processedCount}, Pominięto: {skippedCount}");
}
catch (Exception ex)
{
_logger.LogError(ex, "Błąd podczas przetwarzania emaili");
throw;
}
}
}
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AppConfig": {
"Gmail": {
"CredentialsPath": "C:\\Notes\\credentials.json",
"ApplicationName": "Gmail Email Processor",
"Scopes": [
"https://www.googleapis.com/auth/gmail.readonly"
]
},
"Database": {
"ConnectionString": "Data Source=emails.db"
},
"Ollama": {
"BaseUrl": "http://localhost:11434",
"Model": "llama2",
"SummarizePrompt": "Proszę o zwięzłe podsumowanie poniższego emaila w języku polskim. Skoncentruj się na najważniejszych informacjach, działaniach do podjęcia i kluczowych szczegółach. Maksymalnie 3-4 zdania.\n\nEmail:\n{EMAIL_CONTENT}\n\nPodsumowanie:"
},
"Files": {
"OutputDirectory": "summaries",
"FileNameFormat": "{DATE}_{TIME}_{SUBJECT}_{ID}.md"
},
"Filters": {
"SubjectKeywords": [
"faktura",
"płatność",
"zamówienie",
"projekt",
"spotkanie"
],
"ContentKeywords": [
"pilne",
"deadline",
"termin",
"wymagane"
],
"MaxEmailsPerRun": 50
}
}
}
public class DatabaseService : IDatabaseService
{
private readonly string _connectionString;
private readonly ILogger<DatabaseService> _logger;
public DatabaseService(IOptions<AppConfig> config, ILogger<DatabaseService> logger)
{
_connectionString = config.Value.Database.ConnectionString;
_logger = logger;
}
public async Task InitializeDatabaseAsync()
{
try
{
using var connection = new SqliteConnection(_connectionString);
await connection.OpenAsync();
var createTableCommand = @"
CREATE TABLE IF NOT EXISTS ProcessedEmails (
Id TEXT PRIMARY KEY,
Subject TEXT NOT NULL,
FromAddress TEXT NOT NULL,
ReceivedDate DATETIME NOT NULL,
ProcessedDate DATETIME NOT NULL,
SummaryFilePath TEXT NOT NULL,
IsProcessed BOOLEAN NOT NULL DEFAULT 1
)";
using var command = new SqliteCommand(createTableCommand, connection);
await command.ExecuteNonQueryAsync();
_logger.LogInformation("Baza danych została zainicjalizowana");
}
catch (Exception ex)
{
_logger.LogError(ex, "Błąd podczas inicjalizacji bazy danych");
throw;
}
}
public async Task<bool> IsEmailProcessedAsync(string emailId)
{
try
{
using var connection = new SqliteConnection(_connectionString);
await connection.OpenAsync();
var query = "SELECT COUNT(*) FROM ProcessedEmails WHERE Id = @Id AND IsProcessed = 1";
using var command = new SqliteCommand(query, connection);
command.Parameters.AddWithValue("@Id", emailId);
var count = Convert.ToInt32(await command.ExecuteScalarAsync());
return count > 0;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Błąd podczas sprawdzania statusu emaila {emailId}");
return false;
}
}
public async Task MarkEmailAsProcessedAsync(ProcessedEmail email)
{
try
{
using var connection = new SqliteConnection(_connectionString);
await connection.OpenAsync();
var insertCommand = @"
INSERT OR REPLACE INTO ProcessedEmails
(Id, Subject, FromAddress, ReceivedDate, ProcessedDate, SummaryFilePath, IsProcessed)
VALUES (@Id, @Subject, @FromAddress, @ReceivedDate, @ProcessedDate, @SummaryFilePath, @IsProcessed)";
using var command = new SqliteCommand(insertCommand, connection);
command.Parameters.AddWithValue("@Id", email.Id);
command.Parameters.AddWithValue("@Subject", email.Subject);
command.Parameters.AddWithValue("@FromAddress", email.From);
command.Parameters.AddWithValue("@ReceivedDate", email.ReceivedDate);
command.Parameters.AddWithValue("@ProcessedDate", email.ProcessedDate);
command.Parameters.AddWithValue("@SummaryFilePath", email.SummaryFilePath);
command.Parameters.AddWithValue("@IsProcessed", email.IsProcessed);
await command.ExecuteNonQueryAsync();
_logger.LogInformation($"Email {email.Id} został oznaczony jako przetworzony");
}
catch (Exception ex)
{
_logger.LogError(ex, $"Błąd podczas oznaczania emaila {email.Id} jako przetworzony");
throw;
}
}
public async Task<List<ProcessedEmail>> GetProcessedEmailsAsync()
{
var emails = new List<ProcessedEmail>();
try
{
using var connection = new SqliteConnection(_connectionString);
await connection.OpenAsync();
var query = "SELECT * FROM ProcessedEmails ORDER BY ProcessedDate DESC";
using var command = new SqliteCommand(query, connection);
using var reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
emails.Add(new ProcessedEmail
{
Id = reader.GetString(reader.GetOrdinal("Id")),
Subject = reader.GetString(reader.GetOrdinal("Subject")),
From = reader.GetString(reader.GetOrdinal("FromAddress")),
ReceivedDate = reader.GetDateTime(reader.GetOrdinal("ReceivedDate")),
ProcessedDate = reader.GetDateTime(reader.GetOrdinal("ProcessedDate")),
SummaryFilePath = reader.GetString(reader.GetOrdinal("SummaryFilePath")),
IsProcessed = reader.GetBoolean(reader.GetOrdinal("IsProcessed"))
});
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Błąd podczas pobierania przetworzonych emaili");
}
return emails;
}
}
public class OllamaService : IOllamaService
{
private readonly HttpClient _httpClient;
private readonly OllamaConfig _config;
private readonly ILogger<OllamaService> _logger;
public OllamaService(IOptions<AppConfig> config, ILogger<OllamaService> logger)
{
_config = config.Value.Ollama;
_logger = logger;
_httpClient = new HttpClient();
}
public async Task<string> SummarizeEmailAsync(string emailContent)
{
try
{
var prompt = _config.SummarizePrompt.Replace("{EMAIL_CONTENT}", emailContent);
var requestBody = new
{
model = _config.Model,
prompt = prompt,
stream = false
};
var json = JsonSerializer.Serialize(requestBody);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync($"{_config.BaseUrl}/api/generate", content);
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
var ollamaResponse = JsonSerializer.Deserialize<OllamaResponse>(responseContent);
_logger.LogInformation("Email został pomyślnie podsumowany przez Ollama");
return ollamaResponse.response;
}
else
{
_logger.LogError($"Błąd Ollama API: {response.StatusCode}");
return "Błąd podczas generowania podsumowania";
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Błąd podczas komunikacji z Ollama");
return "Błąd podczas generowania podsumowania";
}
}
private class OllamaResponse
{
public string response { get; set; }
}
}
public class FileService : IFileService
{
private readonly FileConfig _config;
private readonly ILogger<FileService> _logger;
public FileService(IOptions<AppConfig> config, ILogger<FileService> logger)
{
_config = config.Value.Files;
_logger = logger;
}
public async Task<string> SaveSummaryAsync(EmailContent email, string summary)
{
try
{
// Utwórz katalog jeśli nie istnieje
Directory.CreateDirectory(_config.OutputDirectory);
// Generuj nazwę pliku
var sanitizedSubject = SanitizeFileName(email.Subject);
var fileName = _config.FileNameFormat
.Replace("{DATE}", email.ReceivedDate.ToString("yyyy-MM-dd"))
.Replace("{TIME}", email.ReceivedDate.ToString("HH-mm-ss"))
.Replace("{SUBJECT}", sanitizedSubject)
.Replace("{ID}", email.Id);
var filePath = Path.Combine(_config.OutputDirectory, fileName);
// Przygotuj zawartość pliku
var fileContent = $@"# Podsumowanie Email
**Temat:** {email.Subject}
**Od:** {email.From}
**Data otrzymania:** {email.ReceivedDate:yyyy-MM-dd HH:mm:ss}
**ID Email:** {email.Id}
## Podsumowanie
{summary}
## Oryginalna treść
{email.Body}
---
*Wygenerowano: {DateTime.Now:yyyy-MM-dd HH:mm:ss}*
";
await File.WriteAllTextAsync(filePath, fileContent, Encoding.UTF8);
_logger.LogInformation($"Podsumowanie zapisano do pliku: {filePath}");
return filePath;
}
catch (Exception ex)
{
_logger.LogError(ex, "Błąd podczas zapisywania podsumowania do pliku");
throw;
}
}
private string SanitizeFileName(string fileName)
{
var invalidChars = Path.GetInvalidFileNameChars();
foreach (var c in invalidChars)
{
fileName = fileName.Replace(c, '_');
}
// Ogranicz długość nazwy pliku
if (fileName.Length > 50)
{
fileName = fileName.Substring(0, 50);
}
return fileName;
}
}
using HtmlAgilityPack;
using System.Text;
using System.Text.Json;
string maxDate = "2023-10-01";
string prompt = @$"
Oto zawartość strony internetowej. Wyciągnij listę linków (adresów URL) do artykułów dostępnych na tej stronie.
Zwróć tylko linki (1 link na linię) i tylko te, które są opublikowane od daty {maxDate} włącznie.
Zignoruj reklamy, menu, stopki itd.
Treść strony:
{{textContent}}
";
await SemanticScraper.RunAsync("https://cezarywalenciuk.pl/blog/programing/archive", prompt);
class SemanticScraper
{
private static async Task<string> ScrapeHtmlAsync(string url)
{
var web = new HtmlWeb();
var doc = web.Load(url);
return doc.DocumentNode.InnerText;
}
//odpytywanie ollamy jako REST API bez Microsoft.Extensions.AI
private static async Task<string> QueryOllamaAsync(string prompt)
{
var httpClient = new HttpClient();
var body = new
{
model = "deepseek-r1:8b",
prompt = prompt,
stream = false
};
var json = JsonSerializer.Serialize(body);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync("http://localhost:11434/api/generate", content);
return await response.Content.ReadAsStringAsync();
}
public static async Task RunAsync(string url, string prompt)
{
Console.WriteLine($"Scraping: {url}");
var textContent = await ScrapeHtmlAsync(url);
prompt = prompt.Replace("{textContent}", textContent);
var response = await QueryOllamaAsync(prompt);
Console.WriteLine("Wynik z LLM:\n");
Console.WriteLine(response);
}
}
from datetime import datetime, timedelta
from scrapegraphai.graphs import SmartScraperGraph
from scrapegraphai.utils import prettify_exec_info
# Konfiguracja dla Ollama (lokalny model)
graph_config = {
"llm": {
"model": "ollama/llama3.2:3b", # lub inny model dostępny w Ollama
"temperature": 0.1,
"base_url": "http://localhost:11434", # domyślny port Ollama
},
"verbose": True,
"headless": True,
}
def find_recent_articles(url, target_month_year, max_articles=10):
"""
Znajdź najnowsze artykuły z określonego miesiąca i roku
Args:
url: URL strony do przeszukania
target_month_year: format "YYYY-MM" (np. "2024-12")
max_articles: maksymalna liczba artykułów do znalezienia
"""
# Bardziej precyzyjny prompt z przykładem
prompt = f"""
Przeskanuj stronę pod adresem {url} i znajdź wszystkie linki do artykułów opublikowanych w miesiącu {target_month_year}.
WAŻNE: Odpowiedź musi być WYŁĄCZNIE w formacie JSON. Nie dodawaj żadnego tekstu przed ani po JSON-ie.
Format odpowiedzi (przykład):
[
{{
"title": "Tytuł artykułu",
"url": "https://example.com/article1",
"publication_date": "2024-12-15",
"description": "Opis artykułu"
}},
{{
"title": "Drugi artykuł",
"url": "https://example.com/article2",
"publication_date": "2024-12-10",
"description": "Opis drugiego artykułu"
}}
]
Maksymalnie {max_articles} artykułów. Odpowiedź musi być czystym JSON-em bez dodatkowego tekstu.
"""
try:
# Tworzenie grafu scrapującego
smart_scraper_graph = SmartScraperGraph(
prompt=prompt,
source=url,
config=graph_config
)
# Wykonanie scrapowania
result = smart_scraper_graph.run()
# Próba wyciągnięcia JSON-a z odpowiedzi
cleaned_result = extract_json_from_response(result)
return cleaned_result
except Exception as e:
print(f"Błąd podczas scrapowania: {e}")
if hasattr(e, 'llm_output'):
print(f"LLM Output: {e.llm_output}")
return None
def find_articles_with_keywords(url, keywords, date_filter=None):
"""
Znajdź artykuły zawierające określone słowa kluczowe
Args:
url: URL strony do przeszukania
keywords: lista słów kluczowych
date_filter: opcjonalny filtr daty (format "YYYY-MM")
"""
keywords_str = ", ".join(keywords)
date_condition = f" opublikowane w {date_filter}" if date_filter else ""
prompt = f"""
Znajdź wszystkie artykuły na tej stronie, które zawierają którekolwiek z następujących słów kluczowych: {keywords_str}{date_condition}.
Dla każdego pasującego artykułu zwróć:
1. Tytuł
2. Pełny URL
3. Datę publikacji
4. Fragment tekstu zawierający słowo kluczowe
5. Które słowo kluczowe zostało znalezione
Sortuj wyniki według relevancji i daty publikacji.
Format odpowiedzi: JSON lista obiektów z polami:
- title
- url
- publication_date
- relevant_snippet
- matched_keywords
"""
try:
smart_scraper_graph = SmartScraperGraph(
prompt=prompt,
source=url,
config=graph_config
)
result = smart_scraper_graph.run()
return result
except Exception as e:
print(f"Błąd podczas wyszukiwania z słowami kluczowymi: {e}")
return None
def scrape_news_website_articles(url, days_back=30):
"""
Scrape artykułów z witryny informacyjnej z ostatnich N dni
"""
target_date = datetime.now() - timedelta(days=days_back)
date_str = target_date.strftime("%Y-%m-%d")
prompt = f"""
Przeanalizuj tę stronę internetową i znajdź wszystkie artykuły informacyjne
opublikowane od {date_str} do dzisiaj.
Dla każdego artykułu wyodrębnij:
1. Pełny tytuł artykułu
2. Bezpośredni link do artykułu (pełny URL)
3. Datę i czas publikacji
4. Nazwę autora (jeśli dostępna)
5. Kategorię lub sekcję
6. Pierwsze 2-3 zdania artykułu jako podgląd
Ignoruj:
- Reklamy
- Linki do innych stron
- Menu nawigacyjne
- Stopki strony
Zwróć wyniki jako JSON array z obiektami zawierającymi pola:
title, url, publication_date, author, category, preview
Sortuj chronologicznie od najnowszych.
"""
try:
smart_scraper_graph = SmartScraperGraph(
prompt=prompt,
source=url,
config=graph_config
)
result = smart_scraper_graph.run()
return result
except Exception as e:
print(f"Błąd podczas scrapowania witryny informacyjnej: {e}")
return None
# Przykłady użycia
if __name__ == "__main__":
# Upewnij się, że Ollama jest uruchomiona lokalnie
print("Sprawdzanie połączenia z Ollama...")
# Przykład 1: Znajdź artykuły z grudnia 2024
print("\n=== Wyszukiwanie artykułów===")
url1 = "https://cezarywalenciuk.pl/blog/programing/archive" # Zastąp prawdziwym URL
articles_blog = find_recent_articles(url1, "2022-12", max_articles=5)
if articles_blog:
print("Znalezione artykuły:")
print(articles_blog)
# Przykład 2: Wyszukaj artykuły ze słowami kluczowymi
print("\n=== Wyszukiwanie artykułów ze słowami kluczowymi ===")
keywords = ["C#", "ASP.NET", "Windows"]
keyword_articles = find_articles_with_keywords(
url1,
keywords,
date_filter="2022-12"
)
if keyword_articles:
print("Artykuły ze słowami kluczowymi:")
print(keyword_articles)
# Przykład 3: Scraping witryny informacyjnej z ostatnich 7 dni
print("\n=== Najnowsze artykuły z ostatnich 900 dni ===")
news_url = "https://cezarywalenciuk.pl/blog/programing/archive" # Zastąp prawdziwym URL
recent_news = scrape_news_website_articles(news_url, days_back=900)
if recent_news:
print("Najnowsze artykuły:")
print(recent_news)
# Funkcja pomocnicza do ładnego wyświetlania wyników
def display_articles(articles_data, max_display=5):
"""Wyświetl artykuły w czytelnym formacie"""
if not articles_data:
print("Brak danych do wyświetlenia")
return
# Jeśli wynik jest stringiem JSON, spróbuj go sparsować
if isinstance(articles_data, str):
import json
try:
articles_data = json.loads(articles_data)
except:
print("Nie można sparsować danych JSON")
print(articles_data)
return
# Jeśli to lista artykułów
if isinstance(articles_data, list):
print(f"\nZnaleziono {len(articles_data)} artykułów:")
print("=" * 60)
for i, article in enumerate(articles_data[:max_display]):
print(f"\n{i+1}. {article.get('title', 'Brak tytułu')}")
print(f" URL: {article.get('url', 'Brak URL')}")
print(f" Data: {article.get('publication_date', 'Brak daty')}")
if article.get('description'):
print(f" Opis: {article['description'][:100]}...")
else:
print("Wyniki:")
print(articles_data)
using Nest;
using NotesSearchApp.Models;
namespace NotesSearchApp.Services
{
public class NotesService : INotesService
{
private readonly IElasticClient _elasticClient;
private readonly IEmbeddingService _embeddingService;
public NotesService(IElasticClient elasticClient, IEmbeddingService embeddingService)
{
_elasticClient = elasticClient;
_embeddingService = embeddingService;
}
public async Task<SearchResult> SearchNotesAsync(SearchRequest request)
{
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
var searchDescriptor = new SearchDescriptor<Note>()
.Index("notes")
.From((request.Page - 1) * request.PageSize)
.Size(request.PageSize)
.Highlight(h => h
.Fields(
f => f.Field(p => p.Title).PreTags("<mark>").PostTags("</mark>"),
f => f.Field(p => p.Content).PreTags("<mark>").PostTags("</mark>").FragmentSize(150)
)
);
switch (request.SearchType)
{
case SearchType.Text:
searchDescriptor = searchDescriptor.Query(q => q
.MultiMatch(m => m
.Query(request.Query)
.Fields(f => f
.Field(p => p.Title, 2.0)
.Field(p => p.Content)
.Field(p => p.Source)
)
.Type(TextQueryType.BestFields)
.Fuzziness(Fuzziness.Auto)
)
);
break;
case SearchType.Semantic:
var queryEmbedding = await _embeddingService.GetEmbeddingAsync(request.Query);
searchDescriptor = searchDescriptor.Query(q => q
.ScriptScore(ss => ss
.Query(qq => qq.MatchAll())
.Script(s => s
.Source("cosineSimilarity(params.query_vector, 'title_embedding') + 1.0")
.Params(p => p.Add("query_vector", queryEmbedding))
)
)
);
break;
case SearchType.Hybrid:
var hybridEmbedding = await _embeddingService.GetEmbeddingAsync(request.Query);
searchDescriptor = searchDescriptor.Query(q => q
.Bool(b => b
.Should(
s => s.MultiMatch(m => m
.Query(request.Query)
.Fields(f => f
.Field(p => p.Title, 2.0)
.Field(p => p.Content)
.Field(p => p.Source)
)
.Type(TextQueryType.BestFields)
.Fuzziness(Fuzziness.Auto)
.Boost(1.0)
),
s => s.ScriptScore(ss => ss
.Query(qq => qq.MatchAll())
.Script(sc => sc
.Source("cosineSimilarity(params.query_vector, 'title_embedding') + 1.0")
.Params(p => p.Add("query_vector", hybridEmbedding))
)
.Boost(0.8)
)
)
)
);
break;
}
var response = await _elasticClient.SearchAsync<Note>(searchDescriptor);
stopwatch.Stop();
if (!response.IsValid)
{
throw new Exception($"Elasticsearch error: {response.OriginalException?.Message}");
}
var notes = response.Documents.Zip(response.Hits, (doc, hit) => new NoteResult
{
Note = doc,
Score = hit.Score ?? 0,
Highlights = hit.Highlight?.Values
.SelectMany(v => v)
.ToArray() ?? Array.Empty<string>()
}).ToList();
return new SearchResult
{
Notes = notes,
TotalCount = response.Total,
Page = request.Page,
PageSize = request.PageSize,
Query = request.Query,
SearchType = request.SearchType,
ElapsedMilliseconds = stopwatch.Elapsed.TotalMilliseconds
};
}
public async Task<Note?> GetNoteByIdAsync(string id)
{
var response = await _elasticClient.GetAsync<Note>(id, g => g.Index("notes"));
return response.IsValid ? response.Source : null;
}
public async Task<bool> IndexNoteAsync(Note note)
{
// Generuj embedding dla tytułu
note.TitleEmbedding = await _embeddingService.GetEmbeddingAsync(note.Title);
var response = await _elasticClient.IndexAsync(note, i => i
.Index("notes")
.Id(note.Id)
.Refresh(Refresh.WaitFor)
);
return response.IsValid;
}
public async Task<bool> DeleteNoteAsync(string id)
{
var response = await _elasticClient.DeleteAsync<Note>(id, d => d
.Index("notes")
.Refresh(Refresh.WaitFor)
);
return response.IsValid;
}
}
}
import spacy
from spacy import displacy
import sys
from typing import List, Dict, Tuple
class PolishNER:
"""
Klasa do rozpoznawania jednostek nazwanych w języku polskim
"""
def __init__(self, model_name: str = "pl_core_news_sm"):
"""
Inicjalizuje model spaCy dla języka polskiego
Args:
model_name: nazwa modelu spaCy (pl_core_news_sm lub pl_core_news_lg)
"""
self.model_name = model_name
self.nlp = None
self._load_model()
def _load_model(self):
"""Ładuje model spaCy z obsługą błędów"""
try:
self.nlp = spacy.load(self.model_name)
print(f"✓ Załadowano model: {self.model_name}")
print(f"✓ Wersja spaCy: {spacy.__version__}")
except OSError:
print(f"❌ Model '{self.model_name}' nie jest zainstalowany!")
print("Zainstaluj model poleceniem:")
print(f"python -m spacy download {self.model_name}")
print("\nDostępne modele polskie:")
print("- pl_core_news_sm (mały, szybki)")
print("- pl_core_news_lg (duży, dokładniejszy)")
sys.exit(1)
def extract_entities(self, text: str) -> List[Dict]:
"""
Wyodrębnia jednostki nazwane z tekstu
Args:
text: tekst do analizy
Returns:
Lista słowników z informacjami o jednostkach nazwanych
"""
doc = self.nlp(text)
entities = []
for ent in doc.ents:
entity_info = {
'text': ent.text,
'label': ent.label_,
'description': spacy.explain(ent.label_) or 'Nieznany typ',
'start': ent.start_char,
'end': ent.end_char,
'confidence': getattr(ent, 'score', 'N/A')
}
entities.append(entity_info)
return entities
def analyze_text(self, text: str, show_details: bool = True) -> Dict:
"""
Kompleksowa analiza tekstu z NER
Args:
text: tekst do analizy
show_details: czy pokazać szczegółowe informacje
Returns:
Słownik z wynikami analizy
"""
doc = self.nlp(text)
# Wyodrębnij jednostki nazwane
entities = self.extract_entities(text)
# Statystyki
stats = {
'total_tokens': len(doc),
'total_entities': len(entities),
'sentences': len(list(doc.sents)),
'entity_types': len(set(ent['label'] for ent in entities))
}
# Grupuj jednostki według typu
entities_by_type = {}
for ent in entities:
label = ent['label']
if label not in entities_by_type:
entities_by_type[label] = []
entities_by_type[label].append(ent)
results = {
'text': text,
'entities': entities,
'entities_by_type': entities_by_type,
'statistics': stats
}
if show_details:
self._print_analysis(results)
return results
def _print_analysis(self, results: Dict):
"""Wyświetla sformatowane wyniki analizy"""
text = results['text']
entities = results['entities']
entities_by_type = results['entities_by_type']
stats = results['statistics']
print("=" * 80)
print("ANALIZA JEDNOSTEK NAZWANYCH (NER)")
print("=" * 80)
print(f"Tekst: '{text}'")
print(f"Model: {self.model_name}")
# Statystyki - bezpieczne formatowanie
print(f"\n📊 STATYSTYKI:")
print(f" Tokens: {stats.get('total_tokens', 0)}")
print(f" Zdania: {stats.get('sentences', 0)}")
print(f" Jednostki nazwane: {stats.get('total_entities', 0)}")
print(f" Typy jednostek: {stats.get('entity_types', 0)}")
if not entities:
print("\n❌ Nie znaleziono żadnych jednostek nazwanych.")
return
# Wszystkie jednostki - bezpieczne formatowanie
print(f"\n🔍 WSZYSTKIE JEDNOSTKI NAZWANE:")
print(f"{'Nr':<3} {'Tekst':<20} {'Typ':<15} {'Opis':<25} {'Pozycja':<10}")
print("-" * 80)
for i, ent in enumerate(entities, 1):
# Bezpieczne pobieranie wartości
text_val = str(ent.get('text', ''))[:19] # Obetnij długie teksty
label_val = str(ent.get('label', ''))[:14]
desc_val = str(ent.get('description', ''))[:24]
start_val = ent.get('start', 0)
end_val = ent.get('end', 0)
pos = f"{start_val}-{end_val}"
print(f"{i:<3} {text_val:<20} {label_val:<15} {desc_val:<25} {pos:<10}")
# Jednostki według typu
print(f"\n📂 JEDNOSTKI WEDŁUG TYPU:")
for label, ents in entities_by_type.items():
description = spacy.explain(label) or 'Nieznany typ'
print(f"\n{label} ({description}):")
unique_entities = list(set(ent.get('text', '') for ent in ents))
for entity_text in unique_entities:
if entity_text: # Sprawdź czy tekst nie jest pusty
count = sum(1 for ent in ents if ent.get('text', '') == entity_text)
print(f" • {entity_text} ({count}x)")
def get_entity_types_info(self) -> Dict[str, str]:
"""
Zwraca informacje o typach jednostek nazwanych obsługiwanych przez model
"""
# Typowe etykiety w modelach polskich spaCy
common_labels = {
'PER': 'Osoby (imiona, nazwiska)',
'PERSON': 'Osoby (imiona, nazwiska)',
'ORG': 'Organizacje (firmy, instytucje)',
'LOC': 'Lokalizacje (miejsca, regiony)',
'GPE': 'Jednostki geopolityczne (kraje, miasta)',
'MISC': 'Różne (inne jednostki nazwane)',
'DATE': 'Daty i okresy czasu',
'TIME': 'Czas (godziny)',
'MONEY': 'Kwoty pieniężne',
'PERCENT': 'Procenty',
'QUANTITY': 'Ilości i miary',
'ORDINAL': 'Liczby porządkowe',
'CARDINAL': 'Liczby główne'
}
return common_labels
def visualize_entities(self, text: str, style: str = "ent"):
"""
Wizualizuje jednostki nazwane w tekście
Args:
text: tekst do wizualizacji
style: styl wizualizacji ("ent" lub "dep")
"""
doc = self.nlp(text)
if style == "ent" and doc.ents:
print("\n🎨 WIZUALIZACJA JEDNOSTEK NAZWANYCH:")
print("=" * 50)
# Tekst z podświetlonymi jednostkami
highlighted_text = text
offset = 0
for ent in sorted(doc.ents, key=lambda x: x.start_char):
start = ent.start_char + offset
end = ent.end_char + offset
replacement = f"[{ent.text}]({ent.label_})"
highlighted_text = (highlighted_text[:start] +
replacement +
highlighted_text[end:])
offset += len(replacement) - len(ent.text)
print(highlighted_text)
# Legenda
print(f"\n📝 LEGENDA:")
entity_types = set(ent.label_ for ent in doc.ents)
for label in entity_types:
description = spacy.explain(label) or 'Nieznany typ'
print(f" {label}: {description}")
else:
print("❌ Brak jednostek nazwanych do wizualizacji.")
def demo_polish_ner():
"""
Demonstracja działania NER dla języka polskiego
"""
print("🇵🇱 DEMO: Named Entity Recognition dla języka polskiego")
print("=" * 60)
# Inicjalizuj NER
try:
ner = PolishNER("pl_core_news_sm") # lub "pl_core_news_lg"
except SystemExit:
return
# Przykładowe teksty polskie
sample_texts = [
"Jan Kowalski mieszka w Warszawie i pracuje w Microsoft.",
"Prezydent Andrzej Duda spotkał się z premierem Mateuszem Morawieckim w Pałacu Prezydenckim w Warszawie 15 marca 2024 roku.",
"PKN Orlen jest największą polską firmą petrochemiczną. Jej siedziba znajduje się w Płocku.",
"Robert Lewandowski strzelił 3 gole w meczu Bayern Monachium przeciwko Borussii Dortmund na Signal Iduna Park.",
"Uniwersytet Warszawski został założony w 1816 roku. Obecnie studiuje tam około 45 tysięcy studentów.",
"Apple ogłosiło wczoraj nowy iPhone za 4999 złotych. Premiera odbędzie się 20 października w Cupertino."
]
# Pokaż informacje o typach jednostek
print("\n📋 OBSŁUGIWANE TYPY JEDNOSTEK NAZWANYCH:")
entity_types = ner.get_entity_types_info()
for label, desc in entity_types.items():
print(f" {label:<10}: {desc}")
# Analizuj każdy przykład
for i, text in enumerate(sample_texts, 1):
print(f"\n{'#' * 80}")
print(f"PRZYKŁAD {i}/{len(sample_texts)}")
print('#' * 80)
# Analiza NER
results = ner.analyze_text(text, show_details=True)
# Wizualizacja
ner.visualize_entities(text)
if i < len(sample_texts):
input(f"\n⏳ Naciśnij Enter aby przejść do przykładu {i+1}...")
def custom_ner_analysis():
"""
Interaktywna analiza własnego tekstu
"""
print("\n" + "="*60)
print("🔤 ANALIZA WŁASNEGO TEKSTU")
print("="*60)
try:
ner = PolishNER("pl_core_news_sm")
except SystemExit:
return
while True:
print("\nWpisz tekst do analizy (lub 'quit' aby zakończyć):")
user_text = input("Tekst: ").strip()
if user_text.lower() in ['quit', 'exit', 'q']:
break
if not user_text:
print("❌ Pusty tekst!")
continue
print("\n" + "-"*50)
results = ner.analyze_text(user_text, show_details=True)
ner.visualize_entities(user_text)
if __name__ == "__main__":
try:
# Uruchom demo
demo_polish_ner()
# Opcjonalnie: analiza własnego tekstu
response = input("\n🤔 Chcesz przeanalizować własny tekst? (t/n): ")
if response.lower() in ['t', 'tak', 'y', 'yes']:
custom_ner_analysis()
except KeyboardInterrupt:
print("\n\n👋 Zakończono program.")
except Exception as e:
print(f"\n❌ Wystąpił błąd: {e}")
print("Upewnij się, że spaCy jest poprawnie zainstalowane.")
using System.Text;
using System.Text.RegularExpressions;
using WeCantSpell.Hunspell;
// Zapewniamy poprawne kodowanie polskich znaków w konsoli
Console.OutputEncoding = Encoding.UTF8;
string folder = "dictionaries_UTF-8";
string language = "pl_PL";
// Przykładowy tekst OCR do oceny
string ocrText = "ING BANK To jest przykładowy tekst z OCR, który zawiera polskie znaki. A to telefon : 555 555 111";
// Test słownika - sprawdzenie podstawowych polskich słów
// Jeśli to nie działa to nic nie ma sensu
TestDictionary(language, folder);
double score = AssessOcrQuality(ocrText, language, folder);
int rating = ConvertScoreToRating(score);
Console.WriteLine($"Tekst OCR: {ocrText}");
Console.WriteLine($"Wynik oceny: {score:F2}");
Console.WriteLine($"Ocena jakości (0-5): {rating}");
// Szczegółowa analiza
var report = AnalyzeOcrQualityDetailed(ocrText, language, folder);
Console.WriteLine("\nSzczegółowy raport:");
Console.WriteLine(report);
try
{
Guid guid = Guid.NewGuid();
File.WriteAllLines($"wordThatMaybeShouldBeAdded/{guid}.txt", report.InvalidWordsList);
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
/// <summary>
/// Testuje słownik na podstawowych polskich słowach
/// </summary>
/// <param name="language">Kod języka słownika</param>
static void TestDictionary(string language, string folder)
{
try
{
Console.WriteLine("Testowanie słownika...");
var dictionaryPath = $"{folder}/{language}";
// Sprawdź czy pliki słownika istnieją
if (!File.Exists($"{dictionaryPath}.dic"))
Console.WriteLine($"BŁĄD: Plik słownika {dictionaryPath}.dic nie istnieje!");
if (!File.Exists($"{dictionaryPath}.aff"))
Console.WriteLine($"BŁĄD: Plik słownika {dictionaryPath}.aff nie istnieje!");
if (!File.Exists($"{dictionaryPath}.dic") || !File.Exists($"{dictionaryPath}.aff"))
{
Console.WriteLine("Nie można znaleźć plików słownika. Sprawdź ścieżkę i nazwy plików.");
return;
}
var dictionary = WordList.CreateFromFiles($"{dictionaryPath}.dic", $"{dictionaryPath}.aff");
// Spróbuj:
//var dictionaryData = File.ReadAllText($"{dictionaryPath}.dic", Encoding.UTF8);
//var affixData = File.ReadAllText($"{dictionaryPath}.aff", Encoding.UTF8);
//var dictionary = WordList.CreateFromStreams(
// new MemoryStream(Encoding.UTF8.GetBytes(dictionaryData)),
// new MemoryStream(Encoding.UTF8.GetBytes(affixData))
//);
// Lista podstawowych polskich słów do testowania
var testWords = new[] {
"dom", "kot", "pies", "przykładowy", "zawiera", "tekst",
"polski", "język", "słownik", "komputer"
};
Console.WriteLine("Wyniki testu słownika:");
foreach (var word in testWords)
{
bool isWordRecognized = dictionary.Check(word);
Console.WriteLine($" - {word}: {(isWordRecognized ? "rozpoznane" : "nierozpoznane")}");
if (!isWordRecognized)
{
var suggestions = dictionary.Suggest(word).Take(3);
Console.WriteLine($" Sugestie: {string.Join(", ", suggestions)}");
}
}
Console.WriteLine("Test słownika zakończony.\n");
}
catch (Exception ex)
{
Console.WriteLine($"Błąd podczas testowania słownika: {ex.Message}");
Console.WriteLine($"Stack trace: {ex.StackTrace}");
}
}
/// <summary>
/// Ocena jakości tekstu OCR przy użyciu metody słownikowej
/// </summary>
/// <param name="text">Tekst OCR do oceny</param>
/// <param name="language">Kod języka (np. "pl_PL" dla polskiego)</param>
/// <returns>Współczynnik jakości (0.0-1.0)</returns>
static double AssessOcrQuality(string text, string language, string folder)
{
try
{
// Załadowanie słownika Hunspell
var dictionaryPath = $"{folder}/{language}";
var dictionary = WordList.CreateFromFiles($"{dictionaryPath}.dic", $"{dictionaryPath}.aff");
// Dzielenie tekstu na słowa z zachowaniem polskich znaków
var words = ExtractWords(text);
if (words.Length == 0)
return 0.0;
// Lista rozpoznanych i nierozpoznanych słów dla diagnostyki
List<string> recognizedWords = new List<string>();
List<string> unrecognizedWords = new List<string>();
// Zliczanie słów, które istnieją w słowniku
int validWords = 0;
foreach (var word in words)
{
if (dictionary.Check(word))
{
validWords++;
recognizedWords.Add(word);
}
else
{
unrecognizedWords.Add(word);
}
}
// Diagnostyka - wypisz rozpoznane i nierozpoznane słowa
Console.WriteLine("\nDiagnostyka słownika:");
Console.WriteLine($"Rozpoznane słowa ({recognizedWords.Count}): {string.Join(", ", recognizedWords)}");
Console.WriteLine($"Nierozpoznane słowa ({unrecognizedWords.Count}): {string.Join(", ", unrecognizedWords)}");
// Obliczanie współczynnika jakości
double qualityScore = (double)validWords / words.Length;
// Uwzględnienie długości tekstu (krótkie teksty mogą być mniej wiarygodne)
double lengthFactor = Math.Min(1.0, words.Length / 20.0);
qualityScore *= (0.7 + 0.3 * lengthFactor);
return qualityScore;
}
catch (Exception ex)
{
Console.WriteLine($"Błąd podczas oceny jakości OCR: {ex.Message}");
return 0.0;
}
}
/// <summary>
/// Konwertuje współczynnik jakości (0.0-1.0) na ocenę w skali 0-5
/// </summary>
/// <param name="score">Współczynnik jakości (0.0-1.0)</param>
/// <returns>Ocena w skali 0-5</returns>
static int ConvertScoreToRating(double score)
{
// Mapowanie współczynnika jakości na skalę 0-5
if (score < 0.2) return 0;
if (score < 0.4) return 1;
if (score < 0.6) return 2;
if (score < 0.75) return 3;
if (score < 0.9) return 4;
return 5;
}
/// <summary>
/// Wyodrębnia słowa z tekstu, zachowując znaki diakrytyczne
/// </summary>
/// <param name="text">Tekst do analizy</param>
/// <returns>Tablica wyodrębnionych słów</returns>
static string[] ExtractWords(string text)
{
// Konwersja do małych liter z zachowaniem polskich znaków
text = text.ToLowerInvariant();
// Używamy wyrażenia regularnego obsługującego znaki Unicode
return Regex.Split(text, @"[^\p{L}]+")
.Where(w => !string.IsNullOrWhiteSpace(w))
.ToArray();
}
/// <summary>
/// Szczegółowa analiza jakości OCR
/// </summary>
/// <param name="text">Tekst OCR do oceny</param>
/// <param name="language">Kod języka (np. "pl_PL" dla polskiego)</param>
/// <returns>Szczegółowe informacje o jakości OCR</returns>
static OcrQualityReport AnalyzeOcrQualityDetailed(string text, string language, string folder)
{
try
{
var dictionaryPath = $"{folder}/{language}";
// Używamy niestandardowego słownika, jeśli istnieje
var dictionaryToUse = dictionaryPath;
if (!File.Exists($"{dictionaryPath}.dic") || !File.Exists($"{dictionaryPath}.aff"))
{
Console.WriteLine($"UWAGA: Słownik {dictionaryPath} nie istnieje, próba użycia słownika systemowego...");
// Może być potrzebna inna lokalizacja słownika systemowego
dictionaryToUse = "pl";
}
var dictionary = WordList.CreateFromFiles($"{dictionaryToUse}.dic", $"{dictionaryToUse}.aff");
// Wyodrębnienie słów z tekstu
var words = ExtractWords(text);
List<string> validWords = new List<string>();
List<string> invalidWords = new List<string>();
Dictionary<string, List<string>> suggestions = new Dictionary<string, List<string>>();
// Zbiór znanych słów kluczowych - słownikowe rozszerzenie
HashSet<string> knownKeywords = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase)
{
"ocr", "pdf", "xml", "jpeg", "png", "tiff"
};
foreach (var word in words)
{
// Specjalne traktowanie dla akronimów i znanych słów kluczowych
if (knownKeywords.Contains(word))
{
validWords.Add(word);
continue;
}
if (dictionary.Check(word))
validWords.Add(word);
else
{
invalidWords.Add(word);
// Pobierz sugestie poprawek dla niepoprawnych słów
var wordSuggestions = dictionary.Suggest(word)
.ToList();
if (wordSuggestions.Any())
suggestions[word] = wordSuggestions;
// Jeśli słowo ma sugestię samego siebie, to oznacza problem ze słownikiem
if (wordSuggestions.Contains(word))
{
Console.WriteLine($"UWAGA: Słowo '{word}' nie jest rozpoznawane przez słownik, " +
$"ale jest sugerowane jako poprawka dla samego siebie!");
}
}
}
double qualityScore = words.Length > 0 ? (double)validWords.Count / words.Length : 0;
int rating = ConvertScoreToRating(qualityScore);
return new OcrQualityReport
{
TotalWords = words.Length,
ValidWords = validWords.Count,
InvalidWords = invalidWords.Count,
InvalidWordsList = invalidWords,
ValidWordsList = validWords,
Suggestions = suggestions,
QualityScore = qualityScore,
Rating = rating,
UsedDictionary = dictionaryToUse
};
}
catch (Exception ex)
{
Console.WriteLine($"Błąd podczas szczegółowej analizy OCR: {ex.Message}");
return new OcrQualityReport
{
TotalWords = 0,
ValidWords = 0,
InvalidWords = 0,
QualityScore = 0,
Rating = 0,
ErrorMessage = ex.Message
};
}
}
/// <summary>
/// Klasa przechowująca szczegółowe informacje o jakości OCR
/// </summary>
public class OcrQualityReport
{
public int TotalWords { get; set; }
public int ValidWords { get; set; }
public int InvalidWords { get; set; }
public List<string> ValidWordsList { get; set; } = new List<string>();
public List<string> InvalidWordsList { get; set; } = new List<string>();
public Dictionary<string, List<string>> Suggestions { get; set; } = new Dictionary<string, List<string>>();
public double QualityScore { get; set; }
public int Rating { get; set; }
public string? UsedDictionary { get; set; }
public string? ErrorMessage { get; set; }
public override string ToString()
{
if (!string.IsNullOrEmpty(ErrorMessage))
{
return $"Błąd: {ErrorMessage}";
}
var result = $"Łącznie słów: {TotalWords}\n" +
$"Poprawnych słów: {ValidWords}\n" +
$"Niepoprawnych słów: {InvalidWords}\n" +
$"Współczynnik jakości: {QualityScore:F2}\n" +
$"Ocena (0-5): {Rating}\n" +
$"Użyty słownik: {UsedDictionary}";
if (ValidWords > 0)
{
result += "\n\nPoprawne słowa: " + string.Join(", ", ValidWordsList);
}
if (InvalidWords > 0)
{
result += "\n\nNiepoprawne słowa:";
foreach (var word in InvalidWordsList)
{
result += $"\n- {word}";
if (Suggestions.ContainsKey(word) && Suggestions[word].Any())
{
result += $" (sugestie: {string.Join(", ", Suggestions[word])})";
}
}
}
return result;
}
}