Created
July 16, 2025 23:12
-
-
Save jadsongmatos/79abce0bdec55438b7efa7ef1466e99b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import os\n", | |
"import csv\n", | |
"import json\n", | |
"import time\n", | |
"import requests\n", | |
"from collections import defaultdict" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"metadata": { | |
"id": "7NAIsQC5ScHe" | |
}, | |
"outputs": [], | |
"source": [ | |
"# Configurações\n", | |
"PIPED_API = \"https://piapi.ggtyler.dev\" # API Piped\n", | |
"TIMEOUT = (1000/9) # segundos de timeout para a requisição 111,111111111\n", | |
"DELAY = (1/3) # segundos de espera entre requisições\n", | |
"TARGET_TYPES = [\"music_songs\", \"music_videos\", \"music_albums\", \"stream\", \"videos\"]\n", | |
"\n", | |
"def load_progress(path):\n", | |
" \"\"\"Carrega playlists já processadas (para retomar o trabalho).\"\"\"\n", | |
" if not os.path.exists(path):\n", | |
" return defaultdict(list)\n", | |
" with open(path, encoding='utf-8') as f:\n", | |
" data = json.load(f)\n", | |
" pl = defaultdict(list)\n", | |
" for item in data.get(\"playlists\", []):\n", | |
" pl[item[\"name\"]] = item[\"videos\"]\n", | |
" return pl\n", | |
"\n", | |
"\n", | |
"def save_progress(playlists, path):\n", | |
" \"\"\"Salva playlists no formato Piped imediatamente.\"\"\"\n", | |
" piped_format = {\n", | |
" \"format\": \"Piped\",\n", | |
" \"version\": 1,\n", | |
" \"playlists\": [\n", | |
" {\n", | |
" \"name\": name,\n", | |
" \"type\": \"playlist\",\n", | |
" \"visibility\": \"private\",\n", | |
" \"videos\": videos\n", | |
" }\n", | |
" for name, videos in playlists.items()\n", | |
" ]\n", | |
" }\n", | |
" # Grava em arquivo\n", | |
" with open(path, 'w', encoding='utf-8') as f:\n", | |
" json.dump(piped_format, f, ensure_ascii=False, indent=2)\n", | |
"\n", | |
"\n", | |
"def search_video(track_name, artist_name):\n", | |
" \"\"\"Busca um vídeo no YouTube via Piped API.\"\"\"\n", | |
" query = f\"{track_name} {artist_name}\"\n", | |
" params = {\"q\": query, \"filter\": \"music_songs\"}\n", | |
" try:\n", | |
" resp = requests.get(f\"{PIPED_API}/search\", params=params, timeout=TIMEOUT)\n", | |
" resp.raise_for_status()\n", | |
" items = resp.json().get(\"items\", [])\n", | |
" # seleciona pela ordem de preferência de tipo\n", | |
" for t in TARGET_TYPES:\n", | |
" for item in items:\n", | |
" if item.get(\"type\") == t:\n", | |
" return item.get(\"url\")\n", | |
" return None\n", | |
" except Exception as e:\n", | |
" print(f\"❌ Erro ao buscar «{query}»: {e}\")\n", | |
" return None\n", | |
"\n", | |
"\n", | |
"def convert_csv_to_piped_format(csv_path, output_path):\n", | |
" playlists = load_progress(output_path)\n", | |
"\n", | |
" with open(csv_path, newline='', encoding='utf-8') as csvfile:\n", | |
" reader = csv.DictReader(csvfile)\n", | |
" rows = list(reader)\n", | |
"\n", | |
" for idx, row in enumerate(rows, start=1):\n", | |
" playlist_name = row.get('Playlist name') or \"Liked videos\"\n", | |
" track_name = row['Track name'].strip()\n", | |
" artist_name = row['Artist name'].strip()\n", | |
"\n", | |
" # Se já houver esse vídeo, pula\n", | |
" if any(track_name in url for url in playlists[playlist_name]):\n", | |
" print(f\"⏭️ Já processado ({idx}/{len(rows)}): {track_name}\")\n", | |
" continue\n", | |
"\n", | |
" print(f\"🔎 Buscando {idx}/{len(rows)}: {track_name} – {artist_name}\")\n", | |
" url = search_video(track_name, artist_name)\n", | |
" if url:\n", | |
" full_url = f\"https://youtube.com{url}\"\n", | |
" playlists[playlist_name].append(full_url)\n", | |
" # salva progresso imediatamente\n", | |
" save_progress(playlists, output_path)\n", | |
" print(f\"✅ Salvo: {full_url}\")\n", | |
" else:\n", | |
" print(f\"⚠️ Não encontrado: {track_name}\")\n", | |
"\n", | |
" time.sleep(DELAY)\n", | |
"\n", | |
" print(\"🏁 Conversão concluída.\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"colab": { | |
"base_uri": "https://localhost:8080/" | |
}, | |
"id": "sVcOdeD7SiWg", | |
"outputId": "084f8279-80d1-4d74-8d49-b7e07724c79b" | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"🔄 Iniciando conversão do CSV para formato Piped...\n", | |
"🔎 Buscando 1/4920: A Arena do Vazio | O Filme | - Minecraft Em busca da casa automática #329 – \n" | |
] | |
} | |
], | |
"source": [ | |
"print(\"🔄 Iniciando conversão do CSV para formato Piped...\")\n", | |
"convert_csv_to_piped_format(\"seu_arquivo.csv\", \"playlists_piped.json\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
} | |
], | |
"metadata": { | |
"colab": { | |
"provenance": [] | |
}, | |
"kernelspec": { | |
"display_name": "base", | |
"language": "python", | |
"name": "python3" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.12.2" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 0 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment