diff --git a/utils_ai.py b/utils_ai.py new file mode 100644 index 0000000..b51249f --- /dev/null +++ b/utils_ai.py @@ -0,0 +1,123 @@ +import os +from pathlib import Path +from openai import OpenAI + +client = OpenAI() + +def _call_openai_with_prompt(prompt_id: str, prompt_version: str, input_text: str) -> str: + """Helper method to execute a prompt.""" + response = client.responses.create( + prompt={ + "id": prompt_id, + "version": prompt_version + }, + input=[ + { + "role": "user", + "content": [ + { + "type": "input_text", + "text": input_text + } + ] + } + ], + reasoning={ + "summary": "auto" + }, + store=True, + include=[ + "reasoning.encrypted_content", + "web_search_call.action.sources" + ] + ) + + # Try to extract the text response based on the new API format + try: + # If response is a Pydantic model + if hasattr(response, 'output') and response.output: + return response.output[0].content[0].text + # If response is a dictionary + elif isinstance(response, dict) and "output" in response: + return response["output"][0]["content"][0]["text"] + # Fallback for choices (if API changes slightly) + elif hasattr(response, 'choices') and response.choices: + return response.choices[0].message.content + return str(response) + except Exception as e: + print(f"Error parsing response: {e}") + return str(response) + + +def select_best_torrents(torrents_text: str) -> str: + """ + Calls the OpenAI API to select the best torrent IDs using a predefined prompt. + + :param torrents_text: A string containing formatted torrent information. + :return: A string containing the selected torrent IDs, separated by space. + """ + return _call_openai_with_prompt( + prompt_id="pmpt_69ae323e0cf4819082be215f3439bed50122fe479d6e0f2f", + prompt_version="2", + input_text=torrents_text + ) + + +def generate_rename_mapping(directory_text: str) -> dict[str, str]: + """ + Calls the OpenAI API to generate a renaming mapping for files + into a Jellyfin-compatible library format. + + :param directory_text: A string containing the base directory and list of files. + :return: A dictionary mapping source paths to destination paths. + """ + raw_response = _call_openai_with_prompt( + prompt_id="pmpt_69ae4175ba248195acf5b828bcc3360707d31714c556743d", + prompt_version="4", + input_text=directory_text + ) + + mapping = {} + for line in raw_response.splitlines(): + if " -->> " in line: + parts = line.split(" -->> ", 1) + if len(parts) == 2: + mapping[parts[0].strip()] = parts[1].strip() + else: + print(f"Invalid line: {line}") + return mapping + + +def apply_rename_mapping(mapping: dict[str, str], base_src_dir: str | Path, base_dst_dir: str | Path) -> None: + """ + Creates symbolic links from source files to their destinations based on the provided mapping. + Missing directories in the destination paths will be created automatically. + + :param mapping: Dictionary where keys are source paths and values are destination paths. + These can be relative to the provided base directories. + :param base_src_dir: The base directory where the source files reside. + :param base_dst_dir: The base directory where the symbolic links will be created. + """ + src_base = Path(base_src_dir).resolve() + dst_base = Path(base_dst_dir).resolve() + + for src_rel, dst_rel in mapping.items(): + src_path = src_base / Path(src_rel) + dst_path = dst_base / Path(dst_rel) + + # Ensure the source actually exists before creating a link to it + if not src_path.exists(): + print(f"Warning: Source path does not exist, skipping: {src_path}") + continue + + # Create parent directories for the destination if they don't exist + dst_path.parent.mkdir(parents=True, exist_ok=True) + + # Create the symbolic link. If it already exists, gracefully ignore or overwrite. + try: + if dst_path.exists() or dst_path.is_symlink(): + dst_path.unlink() + os.symlink(src_path, dst_path) + print(f"Linked: {dst_path.relative_to(dst_base)} -> {src_path.name}") + except Exception as e: + print(f"Failed to link {src_rel} to {dst_rel}: {e}")