diff --git a/.DS_Store b/.DS_Store index b26a1a04bd5db5c2d8a3c4fbbb8fa3ad97f01247..a901a8a97685f6fab2bf15d1bfdbda39ee89b62a 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/main_tg.py b/main_tg.py index 39d6b1c65991a0796f260e328c455a05e4602109..ef4b2e8356dce8ccc438bf42caaa1a1e835619cc 100644 --- a/main_tg.py +++ b/main_tg.py @@ -94,17 +94,22 @@ if __name__ == "__main__": filename = f'tg-checks/{handle}.csv' if os.path.exists(filename): existing_df = reimport_csv(filename) - max_nr = max(existing_df['nr']) - print(f"Dieser Kanal wurde schon einmal ausgelesen, zuletzt Post Nr.: {max_nr} - seitdem {last_post-max_nr} neue Posts") + start_post = max(existing_df['nr']) + print(f"Dieser Kanal wurde schon einmal ausgelesen, zuletzt Post Nr.: {start_post} - seitdem {last_post-start_post} neue Posts") else: - max_nr = last_post-N + start_post = last_post-N+1 + print(f"Noch nicht gespeichert. Importiere {N} Posts bis zum letzten: {last_post}.") # Lies die aktuellsten Posts, sichere und analysiere sie # - print("Einlesen/mit KI beschreiben: ", end="") - posts = tgc_read_range(handle_str, max_nr+1, last_post) + print("Einlesen: ", end="") + posts = tgc_read_range(handle_str, start_post, last_post, save=False, describe= False) print() # für die Fortschrittsmeldung + print("Inhalte sichern und mit KI beschreiben: ", end="") + hydrated_posts = tg_hydrate(posts) + print() print("Auf KI-Inhalt prüfen: ",end="") - checked_posts = check_tg_list(posts, check_images = True) + # Bearbeitet nur die Posts, für die Inhalte hinterlegt sind + checked_posts = tg_evaluate(hydrated_posts) # n_images = 0 n_ai_images = 0 @@ -118,16 +123,20 @@ if __name__ == "__main__": # Detectora-Score für diesen Text abrufen; wenn über der Schwelle, # KI-Texte um eins hochzählen n_ai_texts += 1 if post.get('detectora_ai_score',0) > DETECTORA_T else 0 - if post['photo'] is not None: - n_images += 1 - ai_score = post.get('aiornot_ai_score',{'confidence': 0})['confidence'] - n_ai_images += 1 if ai_score > AIORNOT_T else 0 if post['video'] is not None: n_videos += 1 ai_score = post['aiornot_ai_score'].get('confidence', 0) n_ai_videos += 1 if ai_score > AIORNOT_T else 0 + elif post['photo'] is not None: + n_images += 1 + ai_score = post.get('aiornot_ai_score') + if ai_score is None: + ai_score = 0 + else: + ai_score = ai_score['confidence'] + n_ai_images += 1 if ai_score > AIORNOT_T else 0 - print(f"In den {N} Posts: ") + print(f"\n\nIn den {N} Posts: ") print(f" - Texte: {n_texts}, davon KI-verdächtig: {n_ai_texts} (Schwelle: {DETECTORA_T})") print(f" - Bilder: {n_images}, davon KI-verdächtig: {n_ai_images} (Schwelle: {AIORNOT_T})") print(f"Ergebnis wird in 'tg-checks/{handle}.csv' mit abgespeichert. ") diff --git a/pyproject.toml b/pyproject.toml index d44266e7d193e9311bbf6ccedc212f44e6e92adf..2be555e8a4ade3c0b401f48d8e5fb58d7856ef15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ authors = [ maintainers = [ {name = "Jan Eggers", email = "jan.eggers@hr.de"}, ] -version = "0.2.4.1" # Neue Versionsnummern für pip-Update +version = "0.2.4.2" # Neue Versionsnummern für pip-Update description = "Bluesky- und Telegram-Konten auf KI-Inhalte checken" requires-python = ">=3.8" dependencies = [ diff --git a/src/aichecker/__init__.py b/src/aichecker/__init__.py index 3359a1319418f92721e24b734b6adcd397189d59..a613ea52e95cfc0f918e0d2b8d64ccd8cd42247f 100644 --- a/src/aichecker/__init__.py +++ b/src/aichecker/__init__.py @@ -3,4 +3,4 @@ from .transcribe import ai_description, convert_mp4_to_mp3, convert_ogg_to_m4a, from .detectora import query_detectora from .aiornot import query_aiornot from .check_wrappers import aiornot_wrapper, detectora_wrapper, bsky_aiornot_wrapper -from .check_tg import tgc_clean, tgc_read, tgc_blockread, tgc_read_url, tgc_profile, tgc_read_range, tgc_read_number, check_tg_list \ No newline at end of file +from .check_tg import tgc_clean, tgc_read, tgc_blockread, tgc_read_url, tgc_profile, tgc_read_range, tgc_read_number, tg_evaluate, tg_hydrate \ No newline at end of file diff --git a/src/aichecker/check_tg.py b/src/aichecker/check_tg.py index cba2c062c6b693b5019edc4a474e9855f9a54fcb..8c9e72e2159c476c7224b6e4e6d570c6197297d6 100644 --- a/src/aichecker/check_tg.py +++ b/src/aichecker/check_tg.py @@ -360,7 +360,7 @@ def tgc_blockread(cname="telegram", nr=None, save=True, describe=False): return posts def tgc_read_range(cname, n1=1, n2=None, save=True, describe = True): - # Liest einen Bereich von Posts + # Liest einen Bereich von Post n1 bis Post n2 # Zuerst: Nummer des letzten Posts holen profile = tgc_profile(cname) # Sicherheitscheck: erste Post-Nummer überhaupt schon gepostet? @@ -372,15 +372,15 @@ def tgc_read_range(cname, n1=1, n2=None, save=True, describe = True): n2 = max_nr posts = [] while n <= n2: - max = n + max = n2 new_posts = tgc_blockread(cname, n, save, describe) for p in new_posts: if p['nr'] > n2: return posts if p['nr'] >= n: posts.append(p) - if p['nr'] > max: - max = p['nr'] + if p['nr'] == n2: + return posts n = max return posts @@ -418,19 +418,15 @@ def tgc_read_number(cname, n = 20, cutoff = None, save=True, describe = True): # Routine checkt eine Post-Liste, wie sie aus den tgc_read... Routinen kommen. # Wenn noch kein KI-Check vorliegt, wird er ergänzt. # Setzt allerdings voraus, dass die entsprechenden Inhalte schon abgespeichert sind. -def check_tg_list(posts, check_images = True): - posts = [p for p in posts if p is not None] - for post in posts: - if 'detectora_ai_score' not in post: - # Noch keine KI-Einschätzung für den Text? + +def tg_evaluate(posts, check_texts = True, check_images = True): + # Nimmt eine Liste von Posts und ergänzt KI-Einschätzung von Detectora + # und AIORNOT. + for post in posts: + if ('detectora_ai_score' not in post) and check_texts: + # Noch keine KI-Einschätzung für den Text? post['detectora_ai_score'] = detectora_wrapper(post['text']) - # Leerzeile für den Fortschrittsbalken - print() - if not check_images: - return - # Okay, es geht weiter: Bilder auf KI prüfen - for post in posts: - if 'aiornot_ai_score' not in post: + if ('aiornot_ai_score' not in post) and check_images: if post['video'] is not None: # Audio des Videos analysieren fname = post['video'].get('file') @@ -442,22 +438,35 @@ def check_tg_list(posts, check_images = True): fname = post['voice'].get('file') post['aiornot_ai_score'] = aiornot_wrapper(convert_ogg_to_mp3(fname), is_image = False) return posts -# Wrapper für die check_tg_list Routine. -# Gibt Resultate als df zurück, arbeitet aber hinter den Kulissen mit -# einer Liste von dicts (anders als check_bsky) def tg_hydrate(posts): # Nimmt eine Liste von Posts und zieht die zugehörigen Dateien, # erstellt Beschreibungen und Transkriptionen. # # Fernziel: Asynchrone Verarbeitung. - return posts - -def tg_evaluate(posts, check_texts = True, check_images = True): - # Nimmt eine Liste von Posts und ergänzt KI-Einschätzung von Detectora - # und AIORNOT. - for post in posts: - + for post in posts: + channel = post['channel'] + b_nr = post['nr'] + # Transcribe video and describe thumbnail + if post['video'] is not None and post['video'].get('file', None) is None: + # Save video to file + video_url = post['video'].get('url') + vfile = save_url(video_url, f"{channel}_{b_nr}_video") + post['video']['file'] = vfile + # Now transcribe video file + post['video']['transcription'] = transcribe(vfile) + # Fun fact: video also saves a thumbnail for good measure + if post['photo'] is not None and post['photo'].get('file', None) is None: + photo_url = post['photo']['url'] + pfile = save_url(photo_url, f"{channel}_{b_nr}_photo") + post['photo']['file'] = pfile + image = base64.b64encode(requests.get(photo_url).content).decode('utf-8') + post['photo']['description'] = gpt4_description(f"data:image/jpeg;base64, {image}") + if post['voice'] is not None and post['voice'].get('file', None) is None: + voice_url = post['voice']['url'] + vfile = save_url(voice_url, f"{channel}_{b_nr}_voice") + post['voice']['file'] = vfile + post['voice']['transcription'] = gpt4_description(vfile) return posts def retrieve_tg_csv(cname, path= "tg-checks"): diff --git a/src/aichecker/check_wrappers.py b/src/aichecker/check_wrappers.py index 4d5f0bdf975304095aa2be94f975015b7b63d1b4..e43fd37f348bc46ee37a2f915e639412b0248262 100644 --- a/src/aichecker/check_wrappers.py +++ b/src/aichecker/check_wrappers.py @@ -8,7 +8,7 @@ import logging # Installieren mit # pip install aiornot -from aiornot import Client +from aiornot import Client, AsyncClient # Konstante d_thresh = .8 # 80 Prozent @@ -81,6 +81,41 @@ def aiornot_wrapper(content, is_image = True): print("\b,") return None +async def async_aiornot_wrapper(content, is_image = True): + # Create a client (reads AIORNOT_API_KEY env) + async_client = AsyncClient() + # Check if the API is up + if not await async_client.is_live(): + logging.error("AIORNOT API nicht erreichbar") + exit(1) + # Check your token + resp = await async_client.check_token() + if not resp.is_valid: + logging.error("AIORNOT-API-Token nicht gültig") + exit(1) + is_url = (content.startswith("http://") or content.startswith("https://")) + if is_image: + if is_url: + response = await async_client.image_report_by_url(content) + else: + response = await async_client.image_report_by_file(content) + else: # (Audio) + if is_url: + response = await async_client.audio_report_by_url(content) + else: + response = await async_client.audio_report_by_file(content) + if response is None: + return response + else: + aiornot_dict = ({ + 'score': response.report.verdict, + # Unterscheidung: Bilder haben den Confidence score im Unter-Key 'ai' + # Audios SOLLTEN eien Confidence-Wert in response.report.confidence haben, haben es aber nicht + 'confidence': response.report.ai.confidence if hasattr(response.report, 'ai') else .99, + 'generator': object_to_dict(response.report.generator) if hasattr(response.report, 'generator') else None, + }) + print(f"\b{'X' if aiornot_dict['score'] != 'human' else '.'}",end="") + return aiornot_dict def bsky_aiornot_wrapper(did,embed): # Verpackung für die AIORNOT-Funktion: diff --git a/src/aichecker/transcribe.py b/src/aichecker/transcribe.py index ee398edf7714770496a330a04fcb92dabdaabe91..65b5e1792cd04f604e6df9490b3acbc666625896 100644 --- a/src/aichecker/transcribe.py +++ b/src/aichecker/transcribe.py @@ -19,7 +19,7 @@ Du erstellst eine deutsche Bildbeschreibung für den Alt-Text. Beschreibe, was auf dem Bild zu sehen ist. Beginne sofort mit der Beschreibung. Sei präzise und knapp. Wenn das Bild lesbaren Text enthält, zitiere diesen Text.""" -client = OpenAI(api_key = os.environ.get('OPENAI_API_KEY')) + # Use GPT-4 mini to describe images OLLAMA = False @@ -27,6 +27,7 @@ def gpt4_description(image_url): # Check a local image by converting it to b64: # image_url = f"data:image/jpeg;base64, {b64_image}" print(".", end="") + client = OpenAI(api_key = os.environ.get('OPENAI_API_KEY')) response = client.chat.completions.create( model="gpt-4o-mini", messages=[