2025, Oct 06 09:33
Windows में mutagen और shutil से MP3 व्यवस्थित: regex की trailing स्पेस से WinError 3 का समाधान
Python, mutagen और shutil से MP3 को कलाकार अनुसार व्यवस्थित करते समय Windows में trailing स्पेस से WinError 3 क्यों होता है, regex के बाद strip() से इसे जल्दी ठीक करें.
मेटाडेटा के आधार पर ऑडियो फ़ाइलों को व्यवस्थित करना एक आम ऑटोमेशन कार्य है, और Python की mutagen के साथ shutil इसकी स्वाभाविक जोड़ी लगती है। फिर भी Windows पर एक छोटा-सा लेकिन महत्वपूर्ण विवरण एक मजबूत वर्कफ़्लो को भी तोड़ सकता है: टैग से निकले डायरेक्टरी नाम के अंत में लगी स्पेस। यदि कुछ .mp3 फ़ाइलों को स्थानांतरित करते समय आपका स्क्रिप्ट WinError 3 देता है—खासकर तब, जब artist टैग में ampersand या कोष्ठक हों—तो जड़ कारण प्रायः मेटाडेटा नहीं, बल्कि regex से विभाजन के दौरान उसके साथ होने वाला परिवर्तन होता है।
समस्या को पुन: उत्पन्न करना
प्रक्रिया सीधी है: कार्य निर्देशिका में फ़ाइलें इकट्ठा करें, artist टैग पढ़ें, उसे प्राथमिक कलाकार तक सामान्य करें, प्रत्येक कलाकार के लिए एक डायरेक्टरी बनाएं, फिर फ़ाइल को वहाँ ले जाएँ। नीचे दिया गया उदाहरण वही तर्क दिखाता है जहाँ विफलता हो सकती है।
import mutagen
import os
import re
import shutil
work_root = os.getcwd()
entry_names = [p for p in os.listdir('.') if os.path.isfile(p)]
for name in entry_names:
    try:
        meta = mutagen.File(name, easy=True)
        if meta is not None and "artist" in meta:
            lead_artist = re.split(r";|,|&|\(|/|\sfeat\.|\sFeat\.|\sft|\sFt|\sx\s", meta["artist"][0])[0].upper()
        else:
            lead_artist = "UNKNOWN"
    except Exception as err:
        lead_artist = "UNKNOWN"
    target_dir = os.path.join(work_root, lead_artist)
    source_path = os.path.join(work_root, name)
    os.makedirs(lead_artist, exist_ok=True)
    if source_path[-2:] != 'py':
        try:
            shutil.move(source_path, target_dir)
            print("moved ", source_path, " to ", target_dir)
        except Exception as err:
            print("Error on: ", name, " -> ", err)
            print(source_path, os.path.exists(source_path))
            print(target_dir, os.path.exists(target_dir))
असल में गड़बड़ी कहाँ होती है
मान लें artist टैग “Mumford & Sons” है। पहला कलाकार अलग करने के लिए regex ampersand और खुलते कोष्ठक जैसी सीमाओं पर विभाजित करता है। इस मामले में “&” पर स्प्लिट करने से बाईं ओर “Mumford ” बचता है—ध्यान दें, अंत में एक स्पेस है। यह स्पेस मामूली दिखती है, पर निर्णायक है।
Windows डायरेक्टरी नामों के अंत में आने वाली स्पेस को अलग तरह से संभालता है। जब os.makedirs को ऐसे path के साथ बुलाया जाता है जिसका अंत स्पेस पर होता है, तो वह trailing स्पेस अनदेखी हो जाती है और डायरेक्टरी उसके बिना बनती है। यानी “MUMFORD ” से बना path डिस्क पर “MUMFORD” नाम की डायरेक्टरी बनाता है। बाद में shutil.move (जो os.rename का उपयोग करता है) फ़ाइल को ऐसे गंतव्य में ले जाने की कोशिश करता है जिसकी स्ट्रिंग में वह trailing स्पेस अभी भी मौजूद है। अनुरोधित गंतव्य और वास्तव में बने डायरेक्टरी नाम के बीच यही असंगति path-not-found त्रुटि (WinError 3) को ट्रिगर करती है।
समस्या मेटाडेटा में अपने आप नहीं है; असल मुद्दा regex से विभाजन और उससे निकले डायरेक्टरी नाम में trailing स्पेस का मेल है। डायरेक्टरी बनाते समय OS वह स्पेस चुपचाप हटा देता है, और उसके बाद का move उसी स्पेस वाली path स्ट्रिंग पर इशारा करता है, जो अस्तित्व में नहीं होती।
इसी वजह से वे उलझाऊ निदान भी समझ आते हैं, जहाँ os.path.exists जाँच में दोनों छपे path वैध लगते हैं। बनी हुई डायरेक्टरी मौजूद है (trailing स्पेस के बिना), लेकिन move ऑपरेशन उस स्ट्रिंग को लक्षित करता है जिसमें trailing स्पेस है, जो किसी वास्तविक path से मेल नहीं खाती।
समाधान
regex लगाने के बाद निकले कलाकार के नाम को whitespace ट्रिम करके सामान्य करें। साधारण strip() पर्याप्त है, जो uppercasing करने और उसे डायरेक्टरी नाम के रूप में इस्तेमाल करने से पहले trailing स्पेस हटा देता है।
import mutagen
import os
import re
import shutil
work_root = os.getcwd()
entry_names = [p for p in os.listdir('.') if os.path.isfile(p)]
for name in entry_names:
    try:
        meta = mutagen.File(name, easy=True)
        if meta is not None and "artist" in meta:
            first_part = re.split(r";|,|&|\(|/|\sfeat\.|\sFeat\.|\sft|\sFt|\sx\s", meta["artist"][0])[0]
            lead_artist = first_part.strip().upper()
        else:
            lead_artist = "UNKNOWN"
    except Exception as err:
        lead_artist = "UNKNOWN"
    target_dir = os.path.join(work_root, lead_artist)
    source_path = os.path.join(work_root, name)
    os.makedirs(lead_artist, exist_ok=True)
    if source_path[-2:] != 'py':
        try:
            shutil.move(source_path, target_dir)
            print("moved ", source_path, " to ", target_dir)
        except Exception as err:
            print("Error on: ", name, " -> ", err)
            print(source_path, os.path.exists(source_path))
            print(target_dir, os.path.exists(target_dir))
यह क्यों मायने रखता है
जब आप फ़ाइल-प्रबंधन पाइपलाइनों का निर्माण करते हैं, तो बने हुए path अक्सर टैग, फ़ाइलनाम या उपयोगकर्ता इनपुट जैसे अर्ध-संरचित डेटा से आते हैं। ऐसे वर्ण जो निर्दोष लगते हैं—विभाजकों के आसपास की स्पेस—प्लेटफ़ॉर्म-विशिष्ट किनारी मामलों को जन्म दे सकते हैं, जिन्हें जाँचना कठिन होता है क्योंकि बीच की क्रियाएँ सफल दिखती हैं। यहाँ डायरेक्टरी बनना path को चुपचाप सामान्य कर देता है, जबकि उसके बाद का move मूल स्ट्रिंग को जस का तस रखता है। हर कदम पर इस्तेमाल हुई ठीक-ठीक स्ट्रिंग को कैप्चर किए बिना, विफलता रहस्यमय लगती है।
यह याद दिलाता है कि सरल normalization चरण parsing के तुरंत बाद और किसी भी फ़ाइल सिस्टम ऑपरेशन से पहले होने चाहिए। केवल whitespace ट्रिम करना इतना पर्याप्त बदलाव है कि Windows पर इरादतन और वास्तविक path के बीच असंगति से बचा जा सके।
निष्कर्ष
यदि Windows पर .mp3 फ़ाइलों को संसाधित करते समय, और artist नामों में “&” या “(” शामिल हों, path संबंधी त्रुटि दिखती है, तो अपने बने हुए डायरेक्टरी नामों के normalization की जाँच करें। os.makedirs और shutil.move में इस्तेमाल करने से पहले, regex से निकले हिस्से पर strip() लागू करें। अगर फिर भी कुछ अटपटा लगे, तो स्रोत और गंतव्य की सटीक स्ट्रिंग्स प्रिंट करें और os.path.exists से उनकी मौजूदगी की जाँच करें ताकि वही वास्तविक मान दिखें जो OS प्राप्त कर रहा है। इससे आपके ऑटोमेशन को टैग में सूक्ष्म whitespace समस्याओं से बचाव मिलता है और आपकी लाइब्रेरी में मूव्स स्थिर और अनुमानयोग्य बने रहते हैं।
यह लेख StackOverflow पर एक प्रश्न (लेखक: jstarr11235) और OldBoy के उत्तर पर आधारित है।