هل تنتهي عادةً من نشر مقاطع الفيديو على يوتيوب باللغة اليابانية فقط؟ في هذه المرة، قمت بإنشاء أداة باستخدام بايثون لترجمة عناوين مقاطع الفيديو ووصفها إلى عدة لغات دفعة واحدة، لذا سأقوم بتوثيق تجربتي.
خلفية إنشاء أداة الترجمة
يوجد في يوتيوب “وظيفة ترجمة العناوين”، ولكن إدخال كل عنوان يدويًا يعد أمرًا مرهقًا للغاية. لذا، قمت بإنشاء سكربت باستخدام بايثون لترجمة العناوين والأوصاف اليابانية تلقائيًا إلى لغات مختلفة حول العالم.
تطوير سريع بأسلوب Vibe Coding
أسلوب التطوير في هذه المرة هو ما يُعرف بـ Vibe Coding. لأنه يتعلق بالبنية التحتية، فلا يوجد أي شيء يتعلق بالتطوير هنا (يضحك).
import os
import json
import logging
from typing import Dict, List, Tuple
from dotenv import load_dotenv
from openai import OpenAI
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.errors import HttpError
# =========================
# الإعدادات
# =========================
BASE_LANG = "ja" # اللغة الأصلية هي اليابانية
# اللغات المدعومة (رمز لغة يوتيوب → الاسم ووصف الجمهور)
LANG_CONFIG: Dict[str, Dict[str, str]] = {
"en": {"name": "US English", "audience": "viewers in North America and other English-speaking countries"},
"es": {"name": "European Spanish", "audience": "viewers in Spain"},
"es-419": {"name": "Latin American Spanish", "audience": "viewers in Mexico and other Latin American countries"},
"fr": {"name": "French", "audience": "viewers in France and other French-speaking regions"},
"de": {"name": "German", "audience": "viewers in Germany, Austria, and Switzerland"},
"pt-BR": {"name": "Brazilian Portuguese", "audience": "viewers in Brazil"},
"ru": {"name": "Russian", "audience": "viewers in Russia and neighboring countries"},
"zh-Hans": {"name": "Simplified Chinese", "audience": "viewers in Mainland China"},
"zh-Hant": {"name": "Traditional Chinese", "audience": "viewers in Taiwan, Hong Kong, and other Traditional Chinese regions"},
"ar": {"name": "Arabic", "audience": "viewers in Arabic-speaking countries"},
"id": {"name": "Indonesian", "audience": "viewers in Indonesia"},
"ko": {"name": "Korean", "audience": "viewers in South Korea"},
}
# نطاقات واجهة برمجة تطبيقات بيانات يوتيوب
SCOPES = ["https://www.googleapis.com/auth/youtube.force-ssl"]
# =========================
# إعدادات السجل
# =========================
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
)
logger = logging.getLogger(__name__)
# تحميل المتغيرات البيئية من .env إذا كانت موجودة
load_dotenv()
# =========================
# إنشاء العميل
# =========================
def get_youtube_client():
"""إرجاع عميل واجهة برمجة تطبيقات بيانات يوتيوب."""
logger.info("بدء مصادقة واجهة برمجة تطبيقات يوتيوب...")
flow = InstalledAppFlow.from_client_secrets_file(
"client_secret.json", SCOPES
)
try:
creds = flow.run_local_server(port=0, timeout=120)
except KeyboardInterrupt:
logger.info("تم إلغاء المصادقة بواسطة المستخدم. سيتم إنهاء البرنامج.")
raise SystemExit(1)
youtube = build("youtube", "v3", credentials=creds)
logger.info("تم الحصول على عميل واجهة برمجة تطبيقات يوتيوب.")
return youtube
def get_openai_client() -> OpenAI:
"""إرجاع عميل OpenAI."""
api_key = os.environ.get("OPENAI_API_KEY")
if not api_key:
logger.error("لم يتم تعيين المتغير البيئي OPENAI_API_KEY.")
raise RuntimeError("OPENAI_API_KEY is not set.")
logger.info("تم تهيئة عميل OpenAI.")
return OpenAI(api_key=api_key)
# =========================
# الترجمة بواسطة OpenAI
# =========================
def localize_for_language(
client: OpenAI,
base_title: str,
base_desc: str,
yt_lang: str,
lang_meta: Dict[str, str],
) -> Tuple[str, str]:
"""
ترجمة عنوان ووصف واحد باستخدام OpenAI.
القيمة المرجعة: (localized_title, localized_description)
"""
language_name = lang_meta["name"]
audience = lang_meta["audience"]
system_prompt = (
"أنت كاتب محتوى يوتيوب خبير ومتخصص في الترجمة.\n"
"تتلقى العنوان والوصف الأصليين باللغة اليابانية لفيديو يوتيوب.\n"
"وظيفتك ليست الترجمة حرفيًا، ولكن ترجمة المحتوى بحيث يبدو طبيعيًا ومقنعًا وجذابًا "
"للغة والثقافة المستهدفة.\n"
"احترم النية الأصلية والموضوع والنغمة (على سبيل المثال: تعليمي، ترفيهي، جاد، غير رسمي، إلخ)، "
"مع تحسينه لمشاهدي يوتيوب في المنطقة المستهدفة.\n"
"تجنب العناوين المضللة والمبالغة، ولكن اجعل العنوان والوصف جذابين وواضحين.\n"
"يجب عليك أيضًا اقتراح علامات تصنيف مناسبة باللغة المستهدفة (وبالإنجليزية إذا كانت مستخدمة بشكل شائع) التي تساعد في الوصول إلى الجمهور المناسب.\n"
"أعد JSON يحتوي على ثلاثة حقول بالضبط: 'title'، 'description'، و 'hashtags'.\n"
"'hashtags' يجب أن تكون مصفوفة JSON من السلاسل، كل سلسلة هي علامة تصنيف تتضمن '#'.\n"
"لا تعليقات إضافية، لا تنسيق، فقط كائن JSON."
)
user_prompt = f"""
[Target language]
- Language: {language_name}
- YouTube language code: {yt_lang}
- Audience: {audience}
[Original Japanese title]
{base_title}
[Original Japanese description]
{base_desc}
[Requirements]
- احتفظ بالمعنى الأساسي والمعلومات الرئيسية للنص الياباني الأصلي.
- قم بتكييف التعبيرات والنغمة بحيث تبدو طبيعية وجذابة في اللغة والثقافة المستهدفة.
- اجعل العنوان موجزًا وجذابًا لليوتيوب (يفضل أن يكون أقل من ~70 حرفًا في هذه اللغة، إذا كان ذلك معقولًا).
- في الوصف، استخدم 2-4 فقرات قصيرة لسهولة القراءة.
- حافظ على نغمة مشابهة للأصل (على سبيل المثال: ودية، فضولية، تعليمية، مريحة، جادة، إلخ).
- لا تضع علامات التصنيف داخل الوصف.
- بدلاً من ذلك، قدم 5-12 علامة تصنيف مناسبة في حقل 'hashtags'، كمصفوفة JSON من السلاسل.
- قم بتضمين علامات تصنيف محددة بالموضوع (متعلقة بمحتوى الفيديو).
- يمكنك أيضًا تضمين بعض علامات التصنيف العامة المتعلقة باليوتيوب أو النوع (على سبيل المثال، حول التنسيق أو الفئة).
- استخدم اللغة المستهدفة فقط (والإنجليزية حيثما كان ذلك طبيعيًا لعلامات التصنيف).
"""
try:
resp = client.chat.completions.create(
model="gpt-4.1-mini", # يمكن تغيير النموذج حسب الحاجة
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
temperature=0.8,
)
except Exception as e:
logger.error(f"خطأ في واجهة برمجة تطبيقات OpenAI: lang={yt_lang}, error={e}")
raise
content = resp.choices[0].message.content
try:
data = json.loads(content)
title = data.get("title", "").strip()
desc = data.get("description", "").strip()
except json.JSONDecodeError as e:
logger.error(f"فشل في تحليل JSON من OpenAI: lang={yt_lang}, error={e}")
logger.error(f"محتوى الاستجابة: {content}")
raise
return title, desc
# =========================
# تحديث الترجمة لفيديو واحد
# =========================
def update_video_localizations(
youtube,
oa_client: OpenAI,
video_id: str,
) -> Tuple[List[str], List[str]]:
"""
تسجيل العناوين/الأوصاف متعددة اللغات لفيديو واحد.
القيمة المرجعة: (قائمة اللغات الناجحة، قائمة اللغات الفاشلة)
"""
logger.info(f"جاري استرجاع معلومات الفيديو: video_id={video_id}")
try:
res = youtube.videos().list(
part="snippet,localizations",
id=video_id,
).execute()
except HttpError as e:
logger.error(f"خطأ في واجهة برمجة تطبيقات يوتيوب (videos.list): video_id={video_id}, error={e}")
return [], list(LANG_CONFIG.keys())
if not res.get("items"):
logger.warning(f"لم يتم العثور على الفيديو: video_id={video_id}")
return [], list(LANG_CONFIG.keys())
video = res["items"][0]
snippet = video["snippet"]
localizations = video.get("localizations", {})
base_title = snippet.get("title", "")
base_desc = snippet.get("description", "")
logger.info(f"العنوان الأصلي: {base_title}")
logger.info(f"الوصف الأصلي: {base_desc[:60]}...") # عرض البداية فقط
# تحديد اللغة الافتراضية بوضوح
snippet["defaultLanguage"] = BASE_LANG
success_langs: List[str] = []
failed_langs: List[str] = []
# الترجمة لكل لغة مستهدفة
for yt_lang, meta in LANG_CONFIG.items():
if yt_lang == BASE_LANG:
continue
logger.info(f" بدء الترجمة: {yt_lang} ({meta['name']})")
try:
loc_title, loc_desc = localize_for_language(
oa_client, base_title, base_desc, yt_lang, meta
)
except Exception:
logger.error(f" فشل في الترجمة: {yt_lang}")
failed_langs.append(yt_lang)
continue
logger.info(f" نتيجة الترجمة للعناوين [{yt_lang}]: {loc_title}")
localizations[yt_lang] = {
"title": loc_title,
"description": loc_desc,
}
success_langs.append(yt_lang)
# تطبيق التحديثات على يوتيوب
update_body = {
"id": video_id,
"snippet": snippet,
"localizations": localizations,
}
try:
youtube.videos().update(
part="snippet,localizations",
body=update_body,
).execute()
logger.info(f"تم تحديث ترجمة الفيديو: video_id={video_id}")
except HttpError as e:
logger.error(f"خطأ في واجهة برمجة تطبيقات يوتيوب (videos.update): video_id={video_id}, error={e}")
# إذا فشل التحديث، يعتبر الفيديو غير ناجح
return [], list(LANG_CONFIG.keys())
return success_langs, failed_langs
# =========================
# مساعدة: تحميل معرفات الفيديو
# =========================
def load_video_ids(path: str = "video_ids.txt") -> List[str]:
if not os.path.exists(path):
logger.error(f"ملف قائمة معرفات الفيديو غير موجود: {path}")
raise FileNotFoundError(path)
ids: List[str] = []
with open(path, encoding="utf-8") as f:
for line in f:
line = line.strip()
if line and not line.startswith("#"):
ids.append(line)
logger.info(f"تم تحميل {len(ids)} معرفات فيديو.")
return ids
# =========================
# الرئيسية
# =========================
def main():
youtube = get_youtube_client()
oa_client = get_openai_client()
video_ids = load_video_ids()
total_videos = len(video_ids)
total_success = 0
total_failed = 0
for idx, vid in enumerate(video_ids, start=1):
logger.info("=" * 60)
logger.info(f"[{idx}/{total_videos}] بدء المعالجة: video_id={vid}")
success_langs, failed_langs = update_video_localizations(
youtube, oa_client, vid
)
logger.info(f"[النتيجة] video_id={vid}")
logger.info(f" اللغات الناجحة: {success_langs}")
if failed_langs:
logger.warning(f" اللغات الفاشلة: {failed_langs}")
if success_langs:
total_success += 1
else:
total_failed += 1
logger.info("=" * 60)
logger.info("تمت معالجة جميع مقاطع الفيديو.")
logger.info(f" عدد مقاطع الفيديو الناجحة: {total_success}")
logger.info(f" عدد مقاطع الفيديو الفاشلة: {total_failed}")
logger.info(f" عدد مقاطع الفيديو المستهدفة: {total_videos}")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
logger.info("تم الكشف عن Ctrl+C، سيتم إنهاء البرنامج.")
raise SystemExit(1)تتوافق بيئة بايثون مع LLM بشكل ممتاز، وتمكنت من تنفيذ المعالجة المعقدة المتعلقة بالواجهة البرمجية بسهولة. يوجد أيضًا واجهة برمجة تطبيقات ليوتيوب، وبما أن هذا الاستخدام عبر GCP، فهو مجاني في هذه الحالة.
ما هو تأثير ذلك؟
“هل ستزداد مشاهدات الفيديو بشكل كبير إذا تم ترجمة العنوان إلى اللغة المحلية؟”
بصراحة، في الوقت الحالي، تأثير ذلك غير معروف. ومع ذلك، كان الهدف من هذا المشروع هو “خلق حالة يمكن لمستخدمي كل ثقافة الوصول إليها بلغتهم الأم” بدلاً من متابعة الأرقام.
من خلال دعم لغات أقل شهرة بالإضافة إلى اللغات الرئيسية مثل الإنجليزية والإسبانية، نضمن إمكانية الوصول إلى جميع الثقافات.
تكلفة واجهة البرمجة منخفضة بشكل غير متوقع
قد يكون هناك من يقلق بشأن تكلفة استخدام واجهة برمجة التطبيقات عند استخدام LLM للترجمة.
ومع ذلك، عند تجربتها، لا تستهلك واجهة برمجة التطبيقات الكثير من الرموز. لأننا لا نقوم بتحليل محتوى الفيديو (الصوت أو الصورة)، بل نقوم فقط بمعالجة ترجمة بيانات النص “العنوان” و “الوصف”، لذا فهي خفيفة جدًا. إذا كنت تستخدم واجهة برمجة التطبيقات المجانية من DeepL، فهي مجانية. قد تكون الترجمة آلية، لكن هذا هو الحال.
خلاصة
أشعر أن الأهم في المستقبل هو مدى قدرة المحتوى الذي تم إنشاؤه على عبور الحدود. فقط أشعر بذلك. لأن هذه المدونة أيضًا تُدفع تلقائيًا إلى عدة لغات…