import streamlit as st import librosa import soundfile as sf import pyrubberband as pyrb from pydub import AudioSegment, effects import os import tempfile import shutil # ====================================================================== # CONFIGURATION AND HEADER # ====================================================================== # ตั้งค่าหน้า Streamlit st.set_page_config(page_title="AutoTune Karaoke Enhancer", page_icon="🎤", layout="centered") # แสดงหัวข้อและคำอธิบายแอปด้วย Markdown st.markdown("""
แปลงเสียงร้องคาราโอเกะของคุณให้เพราะขึ้นแบบมืออาชีพ
""", unsafe_allow_html=True) # แจ้งเตือนผู้ใช้เกี่ยวกับโปรแกรมที่ต้องติดตั้งเพิ่มเติม st.info(""" 💡 **ข้อควรรู้:** แอปนี้ต้องการโปรแกรมภายนอกอย่าง **spleeter** และ **sox** เพื่อให้ทำงานได้สมบูรณ์ โปรดตรวจสอบให้แน่ใจว่าได้ติดตั้งแล้วในระบบ """) # ====================================================================== # CORE PROCESSING FUNCTIONS # ====================================================================== def separate_vocals(input_file, output_dir): """ แยกเสียงร้อง (vocals) ออกจากดนตรี (accompaniment) โดยใช้ Spleeter :param input_file: เส้นทางไฟล์เสียงขาเข้า :param output_dir: โฟลเดอร์สำหรับเก็บไฟล์ขาออก :return: เส้นทางของไฟล์เสียงร้องและดนตรีที่แยกแล้ว """ try: # ใช้ os.system เรียกใช้ Spleeter เพื่อแยกเสียง st.info(f"🔍 กำลังแยกเสียงร้องจากไฟล์: {input_file}") os.system(f"spleeter separate -p spleeter:2stems -o {output_dir} {input_file}") # ค้นหาเส้นทางของไฟล์ที่ถูกสร้างขึ้น base = os.path.splitext(os.path.basename(input_file))[0] vocal_path = os.path.join(output_dir, base, "vocals.wav") accompaniment_path = os.path.join(output_dir, base, "accompaniment.wav") # ตรวจสอบว่าไฟล์ถูกสร้างขึ้นจริงหรือไม่ if not os.path.exists(vocal_path) or not os.path.exists(accompaniment_path): st.error("❌ การแยกเสียงล้มเหลว โปรดตรวจสอบว่า spleeter ได้รับการติดตั้งอย่างถูกต้องและไฟล์เสียงไม่มีปัญหา") return None, None return vocal_path, accompaniment_path except Exception as e: st.error(f"❌ เกิดข้อผิดพลาดในการแยกเสียง: {e}") return None, None def autotune(vocal_file, target_key="C", pitch_shift_strength=1.0): """ ปรับคีย์เสียงร้อง (Autotune) โดยหาคีย์เฉลี่ยและเลื่อนไปหาคีย์เป้าหมาย :param vocal_file: เส้นทางไฟล์เสียงร้อง :param target_key: คีย์เป้าหมาย เช่น "C", "D" :param pitch_shift_strength: ความเข้มข้นของการปรับคีย์ (0.0 ถึง 2.0) :return: เส้นทางของไฟล์เสียงร้องที่ปรับคีย์แล้ว """ st.info("🎯 กำลังปรับคีย์...") y, sr = librosa.load(vocal_file) # คำนวณ pitch ของเสียงร้อง pitches, magnitudes = librosa.piptrack(y=y, sr=sr) pitch_series = [pitches[magnitudes[:, i].argmax(), i] for i in range(pitches.shape[1]) if pitches[magnitudes[:, i].argmax(), i] > 0] if not pitch_series: st.warning("⚠️ ไม่พบข้อมูล pitch ในไฟล์เสียง ไม่สามารถทำการ Autotune ได้") return vocal_file semitones = librosa.hz_to_midi(pitch_series) # คำนวณระยะการเลื่อนคีย์ที่จำเป็น shift = (librosa.note_to_midi(target_key) - round(semitones.mean())) * pitch_shift_strength # ใช้ pyrubberband เพื่อเลื่อนคีย์ y_shifted = pyrb.pitch_shift(y, sr, shift) output_file = "autotuned_vocals.wav" sf.write(output_file, y_shifted, sr) return output_file def enhance_vocals(vocal_file, reverb_wetness=50): """ ปรับปรุงคุณภาพเสียงร้องโดยการใส่ฟิลเตอร์และ Reverb :param vocal_file: เส้นทางไฟล์เสียงร้อง :param reverb_wetness: ความเข้มข้นของ Reverb (0 ถึง 100) :return: เส้นทางของไฟล์เสียงร้องที่ปรับปรุงแล้ว """ st.info("✨ กำลังปรับโทนเสียง...") try: vocal = AudioSegment.from_file(vocal_file) # ใช้ low-pass และ high-pass filter เพื่อลดเสียงรบกวน vocal = vocal.low_pass_filter(8000).high_pass_filter(120) with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_file: temp_path = temp_file.name vocal.export(temp_path, format="wav") # ใช้ SoX เพื่อเพิ่ม Reverb output_file = "enhanced_vocal.wav" os.system(f"sox {temp_path} {output_file} reverb {reverb_wetness} 50 100") os.remove(temp_path) return output_file except Exception as e: st.error(f"❌ เกิดข้อผิดพลาดในการปรับปรุงเสียง: {e}") return vocal_file def mix_tracks(vocal_file, music_file, output_file="final_mix.wav"): """ ผสมเสียงร้องที่ปรับปรุงแล้วกับดนตรี :param vocal_file: เส้นทางไฟล์เสียงร้อง :param music_file: เส้นทางไฟล์เสียงดนตรี :return: เส้นทางของไฟล์เสียงที่ผสมเสร็จแล้ว """ st.info("🎚 กำลังมิกซ์เสียง...") vocal = AudioSegment.from_file(vocal_file) music = AudioSegment.from_file(music_file) # ผสมเสียงและปรับความดังให้สมดุล mixed = effects.normalize(music.overlay(vocal)) mixed.export(output_file, format="wav") return output_file # ====================================================================== # STREAMLIT UI AND EXECUTION FLOW # ====================================================================== # ตัวเลือกให้ผู้ใช้อัปโหลดไฟล์ uploaded_file = st.file_uploader("📂 เลือกไฟล์เสียง (MP3/WAV)", type=["mp3", "wav"]) # ตัวเลือกการปรับแต่งเสียง col1, col2 = st.columns(2) with col1: target_key = st.selectbox("🎯 เลือกคีย์เป้าหมาย", ["C", "D", "E", "F", "G", "A", "B"], help="เลือกคีย์ที่ต้องการให้เสียงร้องถูกปรับ") with col2: pitch_shift_strength = st.slider("🎶 ความเข้ม Autotune", 0.0, 2.0, 1.0, 0.1, help="ปรับความเข้มข้นของการปรับคีย์ (1.0 คือค่าปกติ)") reverb_wetness = st.slider("✨ ความเข้ม Reverb", 0, 100, 50, help="ปรับความก้องของเสียง (Reverb)") # ปุ่มเริ่มประมวลผล if uploaded_file is not None: if st.button("🚀 เริ่มประมวลผล"): progress_bar = st.progress(0) temp_file_path = None output_dir = "output" try: # สร้างไฟล์ชั่วคราวเพื่อเก็บไฟล์เสียงที่อัปโหลด with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as temp_file: temp_file.write(uploaded_file.read()) temp_file_path = temp_file.name # 1. แยกเสียงร้องและดนตรี vocals_path, music_path = separate_vocals(temp_file_path, output_dir) if not vocals_path: st.stop() progress_bar.progress(25) # 2. ปรับคีย์เสียงร้อง tuned_vocals_path = autotune(vocals_path, target_key=target_key, pitch_shift_strength=pitch_shift_strength) progress_bar.progress(50) # 3. ปรับปรุงโทนเสียง enhanced_vocals_path = enhance_vocals(tuned_vocals_path, reverb_wetness=reverb_wetness) progress_bar.progress(75) # 4. ผสมเสียงสุดท้าย final_song_path = mix_tracks(enhanced_vocals_path, music_path) progress_bar.progress(100) st.success("✅ เสร็จแล้ว! ดาวน์โหลดไฟล์ด้านล่าง") st.audio(final_song_path, format="audio/wav") with open(final_song_path, "rb") as f: st.download_button("📥 ดาวน์โหลดไฟล์", f, file_name="karaoke_enhanced.wav") except Exception as e: st.error(f"❌ เกิดข้อผิดพลาดที่ไม่คาดคิด: {e}") finally: # ลบไฟล์และโฟลเดอร์ชั่วคราวทั้งหมดที่สร้างขึ้น if temp_file_path and os.path.exists(temp_file_path): os.remove(temp_file_path) if os.path.exists(output_dir): shutil.rmtree(output_dir) if os.path.exists("autotuned_vocals.wav"): os.remove("autotuned_vocals.wav") if os.path.exists("enhanced_vocal.wav"): os.remove("enhanced_vocal.wav") if os.path.exists("final_mix.wav"): os.remove("final_mix.wav")