بسم الله والصلاه والسلام على رسول الله.
السلام عليكم ورحمة الله وبركاته.
أولا قبل ما نبدأ النقاش، أنا حابب أعتذر لو كنت وصلت فكرة خطأ في النقاش السابق لما قلت إن موضوع عمل Router للـ APIs يكون Bridge ما بين الـschemas القديمة وغير المتسقة، وبين المعيار الموحد اللي إن شاء الله نحاول نوصل له، شبه مستحيل أو صعب جدا.
الفكرة من الـRouter في الأساس إن هو يسهل على الـdevelopers إنهم يبدأوا يستخدموا المعيار الجديد حتى مع الـAPIs القديمة، وفي نفس الوقت يسهل على الـAPIs القديمة نفسها إنها تعمل migration بالتدريج، بحيث لو API عملت migration بشكل كامل في وقت من الأوقات، الموضوع ما يكسرش الاستخدام عند الناس.
وكان تقريبا الأخ محمد، على ما أتذكر، هو اللي اتكلم عن الموضوع ده.
فأنا آسف لو كلامي وقتها اتفهم على إني بحبط من الفكرة أو شايف إنها مش ممكنة.
أنا كل اللي كنت أقصده إن الموضوع شاق، ومحتاج مجهود، وإن جزء كبير من المجهود لازم يتركز الأول في إن إحنا نعمل mapping للمصطلحات والأنظمة القديمة، ونعرف كل API شغال إزاي، وبعدها نبدأ نعمل mapping للحاجة دي للمصطلحات الجديدة والطريقة الجديدة في الاستخدام.
فأنا كان قصدي إن الموضوع هياخد مجهود كتير.
ودلوقتي بعد ما دخلت فيه شوية، أقدر أقول إنه فعلا واخد مجهود مني كتير 😂
وده اللي أنا جاي أتكلم عنه النهاردة.
تكملة للنقاش الأول
النقاش ده يعتبر تكملة للنقاش اللي فتحه م. عبد الله:
نحو معيار موحد لبيانات التلاوات الصوتية للقرآن الكريم
https://community.itqan.dev/d/664
وكان بيتكلم عن مشكلة الاختلافات الكبيرة في شكل البيانات الصوتية بين المصادر المختلفة.
وفي بعض الأحيان الموضوع مش بس اختلاف أسماء attributes، لكن كمان طريقة تمثيل البيانات نفسها بتبقى مختلفة.
مثلا MP3Quran عنده تسمية "moshaf".
ممكن القارئ تكون ليه تلاوة أساسية، وبعدها لو عايز رواية مختلفة أو تلاوة مختلفة لنفس القارئ تدخل على الـattribute اللي اسمه "moshaf".
فالحاجة اللي بالنسبة لينا كبشر ممكن نفهمها من السياق، الـmachine مش هيعرفها إلا لو إحنا قلنا له بشكل صريح إن:
«ده نفس القارئ، لكن دي رواية مختلفة أو طريقة تلاوة مختلفة، ومش قارئ جديد.»
وده جزء من المشكلة اللي بنحاول نحلها.
فكرة الـAudio Router
من وقت النقاش ده، ومن وقت الاجتماع البسيط اللي كنا اجتمعناه السبت قبل اللي فات، كنا اتكلمنا إن إحنا نحاول نعمل حاجة تبقى Audio Router.
الفكرة إنها تجمع الاختلافات الموجودة بين Quran Audio APIs المختلفة، وتقدمها كلها بشكل موحد.
طبعا أنا لما دخلت في الموضوع كنت فاكره هيبقى أسهل من كده شوية 😅
بس الموضوع طلع أصعب بكتير.
أولا بسبب الاختلافات اللي اتكلمنا عنها.
وثانيا لأن مش كل المصادر أصلا بتوفر نفس البيانات.
يعني مثلا معظم المصادر مش بتقول بشكل صريح القارئ بيقرأ بأي رواية، إلا لو الرواية مختلفة عن المعتاد.
ونفس الكلام في بعض الأحيان مع طريقة التلاوة.
EveryAyah مثلا، لو الرواية مختلفة أو طريقة التلاوة مختلفة، غالبا بيظهر ده في الاسم.
غير كده، في نسبة كبيرة من الحالات بتبقى حفص مرتل، لكن طبعا دي برضه حاجة ما ينفعش تعتمد فيها على التخمين وخلاص.
في بعض الحالات لازم تسمع الـaudio نفسه عشان تتأكد.
ووصل الموضوع في بعض البيانات إن كان فيه قارئ مكتوب اسم العيلة بتاعه فقط، وفيه أكتر من قارئ بنفس اسم العيلة.
فكنت محتاج أدخل أسمع الـaudios وأقارن بينهم عشان أعرف مين القارئ المقصود.
يعني الموضوع طلع detective work شوية 😂
الـCanonical Catalog
بعيدا عن المشاكل دي، الطريقة اللي ماشي بيها حاليا إن شاء الله هي إن يبقى عندنا GitHub repository يكون هو الـbackbone بتاع الـRouter.
وفي قلبه يبقى عندنا Canonical Tree of Data أو "catalog.json".
الـCatalog عبارة عن شجرة متفرعة.
الأساس بتاعها:
Reciter
↓
Riwayah
↓
Style
↓
Providers
يعني في الأول عندنا الـReciter، القارئ نفسه.
بعد كده تحت كل قارئ بنشوف الروايات المختلفة المتاحة له:
وبعد كل رواية بنشوف طرق التلاوة المتاحة ليها، زي:
- مرتل
- مجود
- معلم
- ترديد أطفال
- وغيرها على حسب المتاح
وبعد كده تحت كل combination من:
Reciter + Riwayah + Style
والى ممكن نسميها Recitation
بنحط الـProviders اللي فعلا عندهم الصوت ده.
مثال من الـCatalog الحالي
مثلا عندنا القارئ عبد المجيب بن كيران، وفي الـcanonical tree الحالية البيانات بتوصل في النهاية للشكل ده:
{
"abdelmoujib_benkirane": {
"name": {
"ar": "عبد المجيب بن كيران",
"en": "Abdelmoujib Benkirane"
},
"riwayat": {
"warsh_an_nafi": {
"styles": {
"murattal": {
"providers": {
"mp3Quran": [
{
"reciterId": 21199,
"moshafId": 10920,
"server": "https://server16.mp3quran.net/A-Benkirane/Rewayat-Warsh-A-n-Nafi/",
"surahTotal": 114
}
]
}
}
}
}
}
}
}
يعني بدل ما أنا أتعامل مع:
"reciterId" في provider،
و"moshafId" في provider تاني،
واسم folder في provider ثالث،
وأحاول أفهم كل واحد فيهم يقصد إيه...
أنا عندي هوية واحدة واضحة:
عبد المجيب بن كيران
→ ورش عن نافع
→ مرتل
→ MP3Quran
والـprovider-specific data تبقى موجودة في الآخر عشان الـRouter يعرف يتعامل معاها.
الـcanonical data الحالية ولله الحمد مجمعة بيانات EveryAyah وMP3Quran، ولسه طبعا تحت العمل والمراجعة.
أنا راجعت على قد ما أقدر، لكن الأفضل طبعا إن أي حد يقدر يراجع ورايا يراجع، لأن النوع ده من البيانات بالذات محتاج تدقيق.
أنواع الـAudio Sources
وهنا ظهر عندنا اختلاف تاني مهم.
مش كل provider بيوفر الـaudio بنفس الطريقة.
عندنا بشكل عام حاليا صور زي:
Standalone Ayah
الـprovider بيديك ملف audio مستقل لكل آية.
يعني مثلا لو طلبت:
2:255
يبقى عندك audio file خاص بالآية 255 من سورة البقرة فقط.
وده الشكل اللي موجود مثلا في EveryAyah.
Standalone Surah
الـprovider بيديك ملف السورة كاملة.
Segmented Surah
وبرضه هنا عندك ملف السورة كاملة، لكن معاه timestamps أو timeline تقدر من خلالها تحدد مكان كل آية داخل الملف.
يعني بدل ما الآية نفسها يكون ليها file مستقل، ممكن ترجع لك بالشكل المنطقي ده:
Surah audio URL
startMs
endMs
وتشغل الجزء الخاص بالآية من ملف السورة.
والتسميات نفسها لسه تحت العمل، سواء "standalone" أو "segment" أو غيرها، لكن الفكرة الأساسية إن لازم المعيار نفسه ما يتعاملش مع الاتنين كأنهم حاجة واحدة، لأنهم فعليا مش نفس representation.
طب إيه فايدة ترتيب الـTree بالشكل ده؟
الفكرة إن الـaccess يبقى طبيعي جدا.
عندي في الأول القارئ:
Reciter
بعده الرواية:
Riwayah
بعدها طريقة التلاوة:
Style
وبعدها الـproviders المتاحة للتلاوة دي.
يعني مثلا من ناحية استخدام الـpackage نفسها، المفروض يبقى عندي object زي:
Reciters
فمجرد ما أبدأ أختار منه قارئ، الـTypeScript يعرف إيه الروايات المتاحة للقارئ ده.
مثلا:
Reciters.MahmoudAlhusary
وبعدها:
Reciters.MahmoudAlhusary.Hafs
وبعدها:
Reciters.MahmoudAlhusary.Hafs.Murattal
فأنا مش محتاج أحفظ IDs ولا أسماء providers ولا أعرف إن القارئ ده رقمه كام في EveryAyah ورقمه كام في MP3Quran.
الـCatalog هو اللي عامل الـmapping ده كله.
الـRouter نفسه
أنا بصراحة كنت أفضل أتكلم على الـRouter بالتفصيل في نقاش منفصل، لأن الموضوع لو دخلنا فيه هنا هنقسم الحاجة الواحدة سبعتاشر مستوى، وأنا عارف إني بالفعل بقسم الحاجة الواحدة كذا مستوى، فمعلش استحملوني 😂
لكن عشان الصورة تبقى واضحة، الـRouter حاليا الفكرة الأساسية فيه قايمة على حاجتين رئيسيتين:
getAyah()
queryCatalog()
أولا: "getAyah"
دي بكل بساطة الوظيفة اللي بتقول لها:
أنا عايز الآية دي.
أو range من الآيات.
وتحدد لها:
- القارئ
- الرواية
- طريقة التلاوة
مثلا الفكرة تبقى قريبة من:
const result = await getAyah({
ayah: '2:255',
recording: Reciters.MahmoudAlhusary.Hafs.Murattal
})
وأنا كـdeveloper ما يهمنيش بقى الصوت جه من EveryAyah ولا MP3Quran ولا provider تاني.
الـRouter يستخدم الـCatalog ويعرف الـmapping الصحيح، وبعدها يرجع لي الـaudio source بالشكل الموحد.
وهنا بقى تظهر فايدة الـcanonical tree.
لأن وأنا بكتب:
Reciters.
المفروض أعرف كل القراء المتاحين.
ولما أختار قارئ:
Reciters.SomeReciter.
أعرف الروايات المتاحة له فقط.
ولما أختار الرواية، أعرف طرق التلاوة المتاحة لها فقط.
فالموضوع يبقى type-safe وسهل في الاستخدام، ومش محتاج developer يعرف كل تفاصيل كل provider.
ثانيا: الـDiscovery أو "queryCatalog"
طيب ده كويس جدا لو أنا كـdeveloper عارف أنا عايز مين.
لكن لو أنا بعمل application وعايز أخلي الـuser نفسه هو اللي يختار؟
هنا بقى ييجي الجزء التاني:
queryCatalog()
الـcanonical tree نفسها في النهاية مجرد data، فتقدر طبعا تستخدمها وتفصصها بنفسك.
لكن لو مش عايز تفصصها وتتعب نفسك، يبقى عندك discovery function تعمل لك ده.
مثلا:
queryCatalog().reciters
ترجع لك كل القراء المتاحين.
ولو قلت:
queryCatalog({
reciter: 'mahmoud_alhusary'
})
ترجع لك كل ما يتعلق بالقارئ ده:
- الروايات المتاحة
- طرق التلاوة
- الـrecordings
- الـproviders المتاحة
ولو مثلا أنا مش فارق معايا القارئ، لكن عايز كل الناس اللي عندهم رواية ورش:
queryCatalog({
riwayah: 'warsh_an_nafi'
})
ترجع لي كل القراء اللي عندهم ورش.
ولو مثلا أنا عايز المعلم، زي مصحف الحصري المعلم مثلا، أقدر أبحث بالـstyle، ويرجع لي كل القراء اللي عندهم النوع ده، ومعاه الروايات المتاحة لكل واحد.
ونفس الكلام لو عملت combination بين أكتر من حاجة.
يعني الفكرة إن إنت بتديله اللي إنت عارفه، وهو يرجع لك باقي الـfacets المتاحة المتعلقة بيه.
وده يخليك تستخدمه بسهولة في UI مثلا:
اختار القارئ
↓
اختار الرواية المتاحة له
↓
اختار طريقة التلاوة المتاحة
أو حتى تبدأ بالعكس:
أنا عايز ورش
↓
مين القراء المتاحين؟
أو:
أنا عايز معلم
↓
مين القراء والروايات اللي عندهم معلم؟
بحيث الموضوع يبقى مفتوح للـdeveloper يعمل mix and match بالطريقة المناسبة للـapplication بتاعه.
ليه الـCatalog واخد الوقت الأكبر؟
أنا حاليا مركز أكتر على الـstructure وعلى الـcanonical catalog قبل ما أطلع حاجة testable أو physical بشكل كويس.
لأن في رأيي ده هو الـcore بتاع الـservice كلها.
الـAPI Router في النهاية شغلته الأساسية إنه يعمل mapping ما بين الأنظمة والـAPIs القديمة وبين الـinterface الموحد الجديد.
فلو الـmapping نفسه غلط، كل اللي فوقه هيبقى غلط.
وعشان كده الـcanonical tree هي أكتر جزء واخد وقت حاليا، لأن الموضوع مش مجرد إني أجمع IDs.
لازم أعرف:
- هل دول فعلا نفس القارئ؟
- الرواية إيه؟
- طريقة التلاوة إيه؟
- الـprovider بيمثلها إزاي؟
- هل عنده كل السور ولا جزء منها؟
- هل الصوت standalone ولا داخل سورة كاملة؟
- ولو نفس التسجيل موجود في provider تاني، إزاي أربط الاتنين بنفس الهوية؟
نقطة إضافية: إزاي نفرق بين تسجيلين لنفس القارئ والرواية وطريقة التلاوة؟
وحاجة كمان قبل ما أخلص إن شاء الله عشان ما أطولش عليكم.
أنا قرأت آخر التعليقات اللي كانت اتقالت في المناقشة السابقة بتاعة م. عبد الله، وفعلا فيه نقطة مهمة لازم نسلط عليها الضوء برضه، وهي التفريق ما بين التسجيلات نفسها.
يعني ممكن يكون عندي نفس القارئ، ونفس الرواية، ونفس طريقة التلاوة، لكن فيه أكتر من تسجيل.
مثلا الشيخ المنشاوي ممكن يبقى عنده تسجيل قديم وتسجيل جديد، والاتنين:
محمد صديق المنشاوي
→ حفص عن عاصم
→ مرتل
فدلوقتي أنا تحت "Murattal" عندي تسجيلين.
أفرق بينهم إزاي؟
دي نقطة برضه محتاجة تتحدد في الـschema.
ممكن مثلا يبقى عندنا attributes إضافية على مستوى الـrecording نفسه، زي تاريخ التسجيل لو معروف، أو وصف يفرق بين النسخ، أو أي metadata مناسبة نقدر من خلالها نحدد إن ده التسجيل القديم وده التسجيل الجديد.
والتسمية والطريقة نفسها طبعا مفتوحة للنقاش.
الفكرة الأساسية بس إن Reciter + Riwayah + Style مش بالضرورة يكونوا unique recording.
ممكن combination واحد يبقى تحته أكتر من recording.
وده شيء الـcanonical tree لازم تعرف تمثله.
والـProvider نفسه محتاج metadata واضحة
وبرضه كل provider لازم يبقى جواه attributes تحدد هو بيوفر الـaudio بإيه بالضبط.
يعني مثلا "mp3Quran" ممكن يبقى object جواه array من الـavailable recordings للشخص ده.
وكل واحدة منهم لازم يبقى واضح جواها:
- هل هي "standalone ayah"؟
- ولا "standalone surah"؟
- ولا "segmented surah"؟
- هل فيه timestamps؟
- إيه الـcoverage المتاح؟
- وإيه الطريقة اللي المفروض الـconsumer يتعامل بيها مع الـaudio؟
لأن أنا مش عايز أرجع للـdeveloper URL وخلاص، وبعدها أسيبه يخمن هو جاله إيه.
لازم يبقى مع الـdata نفسها flag أو metadata واضحة تقول له:
«الصوت ده تتعامل معاه بالطريقة الفلانية.»
بحيث الـconsumer أو الـuser بتاع الـSDK يعرف هو استلم إيه بالضبط ويقدر يتعامل معاه بدون لخبطة.
الـCatalog نفسه هيبقى Human Audited
ودي بقى النقطة المؤلمة شوية 😂
الـcanonical tree أو الـCatalog في الأول وفي الآخر هيبقى manually audited وhuman verified.
للأسف، للأسف، للأسف 😅
أنا زعلان وأنا بقولها، بس مش شايف حاليا طريقة نقدر نخلي AI يعمل الـverification بالكامل، أو نعمل automation للموضوع كله بشكل موثوق.
ممكن طبعا نعمل automation تساعد في جمع البيانات، وتطلع الاختلافات، وتقارن الـIDs، وتقلل الشغل اليدوي.
وحاليا فى جزء شغال عليه لكده.
لكن في الآخر فيه حاجات لازم بني آدم يراجعها.
زي مثلا:
- هل القارئ ده فعلا هو الشخص المقصود؟
- هل التسجيل فعلا بالرواية دي؟
- هل طريقة التلاوة دي صحيحة؟
- هل تسجيلين من providerين مختلفين هما نفس التسجيل أصلا ولا تسجيلين مختلفين؟
وفي بعض الحالات زي ما قلت، أنا نفسي كنت محتاج أسمع الـaudio عشان أعرف مين القارئ.
فصعب جدا نخلي الـmachine يعمل verification للحاجات دي كلها.
وعشان كده، بما إن الـCatalog أصلا manually audited، فإضافة metadata جديدة أو تعديل الـstructure بعدين مش مشكلة كبيرة بإذن الله.
المهم إننا نوصل للـattributes الصح اللي تخلي البيانات واضحة ومحددة، وتخلي اللي استلم recording يعرف بالضبط إيه التسجيل اللي معاه وإزاي يتعامل معاه.
وإحنا حاليا لسه قبل ما نبني بشكل كبير فوق الـstructure دي، فدي أحسن مرحلة إن أي تعديل يحصل.
أنا مفتوح جدا لأي تعديل أو اقتراح أو اعتراض على الـstructure أو التسميات أو طريقة تمثيل البيانات.
وفي الأول وفي الآخر ده مشروع مجتمعي، والفكرة إن المجتمع كله يشارك فيه بإذن الله، سواء بمراجعة البيانات، أو اقتراح الـschema، أو تصحيح الأخطاء، أو تحسين طريقة الـmapping.
فلو فيه طريقة أحسن لتمثيل:
"Reciter → Riwayah → Style → Recording → Provider"
أو حتى اعتراض على إن الترتيب نفسه يبقى بالشكل ده، أو طريقة أحسن للتفريق بين التسجيلات والـaudio representations المختلفة، فده بالضبط الوقت المناسب إننا نتناقش فيه قبل ما نبني حاجات أكتر فوقه.
وربنا ييسر الحال، والله المستعان.
وبعتذر على الإطالة والسلام عليكم ورحمه الله وبركاته.
ودمتم فى امان الله.