编写民间童谣播放器,按地域分类(陕北,江南)等等,播放童谣,搭配歌词和插画。

1 阅读7分钟

我将为您创建一个完整的民间童谣播放器程序。这个项目包含多个模块,支持按地域分类播放童谣。

项目结构

folk_rhyme_player/ ├── main.py # 主程序入口 ├── config.py # 配置文件 ├── data_manager.py # 数据管理模块 ├── audio_player.py # 音频播放模块 ├── ui_components.py # UI组件模块 ├── folk_rhymes_data.json # 童谣数据文件 ├── README.md # 使用说明 └── assets/ # 资源文件夹 ├── images/ # 插画图片 └── audio/ # 音频文件

  1. 配置文件 (config.py)

""" 配置文件 - 定义应用程序的基本配置 """

import os

应用基本配置

APP_NAME = "民间童谣播放器" VERSION = "1.0.0" WINDOW_SIZE = (800, 600)

文件路径配置

BASE_DIR = os.path.dirname(os.path.abspath(file)) DATA_FILE = os.path.join(BASE_DIR, "folk_rhymes_data.json") ASSETS_DIR = os.path.join(BASE_DIR, "assets") IMAGES_DIR = os.path.join(ASSETS_DIR, "images") AUDIO_DIR = os.path.join(ASSETS_DIR, "audio")

支持的音频格式

SUPPORTED_AUDIO_FORMATS = ['.mp3', '.wav', '.ogg', '.m4a']

界面颜色主题

COLORS = { 'primary': '#FF6B6B', 'secondary': '#4ECDC4', 'background': '#F7F9FC', 'text': '#2C3E50', 'accent': '#45B7D1' }

地域分类

REGIONS = ['陕北', '江南', '东北', '广东', '四川', '北京']

  1. 数据管理模块 (data_manager.py)

""" 数据管理模块 - 负责加载和管理童谣数据 """

import json import os from config import DATA_FILE, REGIONS

class FolkRhymeDataManager: def init(self): self.rhymes_data = {} self.load_data()

def load_data(self):
    """从JSON文件加载童谣数据"""
    try:
        if os.path.exists(DATA_FILE):
            with open(DATA_FILE, 'r', encoding='utf-8') as f:
                self.rhymes_data = json.load(f)
            print(f"成功加载 {len(self.rhymes_data)} 首童谣")
        else:
            print("数据文件不存在,创建示例数据...")
            self.create_sample_data()
    except Exception as e:
        print(f"加载数据时出错: {e}")
        self.create_sample_data()

def create_sample_data(self):
    """创建示例童谣数据"""
    sample_data = {
        "小白菜": {
            "region": "陕北",
            "lyrics": "小白菜呀地里黄呀\n三两岁上没了娘呀\n跟着爹爹还好过呀\n就怕爹爹娶后娘呀",
            "audio_file": "xiaobaicai.mp3",
            "image_file": "xiaobaicai.jpg",
            "description": "陕北传统童谣,描述失去母亲的孩子的悲伤"
        },
        "茉莉花": {
            "region": "江南",
            "lyrics": "好一朵美丽的茉莉花\n好一朵美丽的茉莉花\n芬芳美丽满枝桠\n又香又白人人夸",
            "audio_file": "molihua.mp3",
            "image_file": "molihua.jpg",
            "description": "江南经典民歌,以茉莉花比喻美丽姑娘"
        },
        "丢手绢": {
            "region": "北京",
            "lyrics": "丢手绢,丢手绢\n轻轻地放在小朋友的后面\n大家不要告诉他\n快点快点捉住他\n快点快点捉住他",
            "audio_file": "dioushijuan.mp3",
            "image_file": "dioushijuan.jpg",
            "description": "北京传统儿童游戏歌曲"
        },
        "数鸭子": {
            "region": "东北",
            "lyrics": "门前大桥下\n游过一群鸭\n快来快来数一数\n二四六七八",
            "audio_file": "shuyazi.mp3",
            "image_file": "shuyazi.jpg",
            "description": "东北地区流传的儿童歌曲"
        }
    }
    
    self.rhymes_data = sample_data
    self.save_data()

def save_data(self):
    """保存数据到JSON文件"""
    try:
        with open(DATA_FILE, 'w', encoding='utf-8') as f:
            json.dump(self.rhymes_data, f, ensure_ascii=False, indent=2)
        print("数据保存成功")
    except Exception as e:
        print(f"保存数据时出错: {e}")

def get_all_regions(self):
    """获取所有地域分类"""
    return REGIONS

def get_rhymes_by_region(self, region):
    """根据地域获取童谣列表"""
    result = []
    for name, info in self.rhymes_data.items():
        if info.get('region') == region:
            result.append({
                'name': name,
                **info
            })
    return result

def get_rhyme_by_name(self, name):
    """根据名称获取特定童谣信息"""
    return self.rhymes_data.get(name)

def search_rhymes(self, keyword):
    """搜索童谣"""
    results = []
    for name, info in self.rhymes_data.items():
        if (keyword.lower() in name.lower() or 
            keyword.lower() in info.get('lyrics', '').lower()):
            results.append({
                'name': name,
                **info
            })
    return results

3. 音频播放模块 (audio_player.py)

""" 音频播放模块 - 负责音频文件的播放控制 """

import pygame import threading import time from config import SUPPORTED_AUDIO_FORMATS

class AudioPlayer: def init(self): pygame.mixer.init() self.current_track = None self.is_playing = False self.is_paused = False self.position = 0 self.length = 0 self.play_thread = None

def play(self, audio_file):
    """播放音频文件"""
    try:
        if not os.path.exists(audio_file):
            print(f"音频文件不存在: {audio_file}")
            return False
        
        # 停止当前播放
        self.stop()
        
        # 加载并播放音频
        pygame.mixer.music.load(audio_file)
        pygame.mixer.music.play()
        
        self.current_track = audio_file
        self.is_playing = True
        self.is_paused = False
        
        # 启动播放进度监控线程
        self.start_progress_monitor()
        
        print(f"开始播放: {audio_file}")
        return True
        
    except pygame.error as e:
        print(f"播放音频时出错: {e}")
        return False

def pause(self):
    """暂停播放"""
    if self.is_playing and not self.is_paused:
        pygame.mixer.music.pause()
        self.is_paused = True
        print("播放已暂停")
    elif self.is_paused:
        pygame.mixer.music.unpause()
        self.is_paused = False
        print("播放已恢复")

def stop(self):
    """停止播放"""
    if pygame.mixer.music.get_busy():
        pygame.mixer.music.stop()
    
    self.is_playing = False
    self.is_paused = False
    self.position = 0
    
    if self.play_thread and self.play_thread.is_alive():
        self.play_thread = None

def is_currently_playing(self):
    """检查是否正在播放"""
    return self.is_playing and not self.is_paused and pygame.mixer.music.get_busy()

def start_progress_monitor(self):
    """启动播放进度监控"""
    def monitor():
        while self.is_playing and pygame.mixer.music.get_busy():
            time.sleep(0.1)
        
        if self.is_playing:
            # 播放结束
            self.is_playing = False
            self.position = 0
    
    self.play_thread = threading.Thread(target=monitor, daemon=True)
    self.play_thread.start()

def set_volume(self, volume):
    """设置音量 (0.0 - 1.0)"""
    pygame.mixer.music.set_volume(max(0.0, min(1.0, volume)))

def get_volume(self):
    """获取当前音量"""
    return pygame.mixer.music.get_volume()

4. UI组件模块 (ui_components.py)

""" UI组件模块 - 自定义UI组件 """

import tkinter as tk from tkinter import ttk, messagebox from PIL import Image, ImageTk import os from config import COLORS, IMAGES_DIR

class RhymeCard(tk.Frame): """童谣卡片组件""" def init(self, parent, rhyme_data, command=None, **kwargs): super().init(parent, **kwargs)

    self.rhyme_data = rhyme_data
    self.command = command
    self.configure(bg=COLORS['background'], relief='raised', bd=2)
    
    self.setup_ui()

def setup_ui(self):
    # 标题
    title_label = tk.Label(
        self, 
        text=self.rhyme_data['name'],
        font=('微软雅黑', 14, 'bold'),
        bg=COLORS['background'],
        fg=COLORS['text']
    )
    title_label.pack(pady=(10, 5))
    
    # 地域标签
    region_label = tk.Label(
        self,
        text=f"地域: {self.rhyme_data['region']}",
        font=('微软雅黑', 10),
        bg=COLORS['secondary'],
        fg='white',
        padx=10,
        pady=2
    )
    region_label.pack(pady=5)
    
    # 描述
    desc_text = self.rhyme_data.get('description', '')
    if len(desc_text) > 50:
        desc_text = desc_text[:50] + "..."
    
    desc_label = tk.Label(
        self,
        text=desc_text,
        font=('微软雅黑', 9),
        bg=COLORS['background'],
        fg=COLORS['text'],
        wraplength=200
    )
    desc_label.pack(pady=(5, 10))
    
    # 绑定点击事件
    self.bind("<Button-1>", self.on_click)
    title_label.bind("<Button-1>", self.on_click)
    region_label.bind("<Button-1>", self.on_click)
    desc_label.bind("<Button-1>", self.on_click)

def on_click(self, event):
    if self.command:
        self.command(self.rhyme_data)

class LyricDisplay(tk.Frame): """歌词显示组件""" def init(self, parent, **kwargs): super().init(parent, **kwargs)

    self.setup_ui()

def setup_ui(self):
    # 标题
    title_label = tk.Label(
        self,
        text="歌词",
        font=('微软雅黑', 16, 'bold'),
        bg=COLORS['background'],
        fg=COLORS['text']
    )
    title_label.pack(pady=(0, 10))
    
    # 歌词文本框
    self.lyric_text = tk.Text(
        self,
        height=15,
        width=50,
        font=('微软雅黑', 12),
        wrap=tk.WORD,
        bg='white',
        fg=COLORS['text'],
        relief='solid',
        bd=1
    )
    self.lyric_text.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
    
    # 滚动条
    scrollbar = ttk.Scrollbar(self.lyric_text)
    scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
    self.lyric_text.config(yscrollcommand=scrollbar.set)
    scrollbar.config(command=self.lyric_text.yview)

def display_lyrics(self, lyrics):
    """显示歌词"""
    self.lyric_text.delete(1.0, tk.END)
    self.lyric_text.insert(1.0, lyrics)
    self.lyric_text.config(state=tk.DISABLED)

class ImageViewer(tk.Frame): """图像查看器组件""" def init(self, parent, **kwargs): super().init(parent, **kwargs)

    self.current_image = None
    self.setup_ui()

def setup_ui(self):
    # 标题
    title_label = tk.Label(
        self,
        text="插画欣赏",
        font=('微软雅黑', 16, 'bold'),
        bg=COLORS['background'],
        fg=COLORS['text']
    )
    title_label.pack(pady=(0, 10))
    
    # 图像标签
    self.image_label = tk.Label(
        self,
        bg='white',
        relief='solid',
        bd=1
    )
    self.image_label.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

def display_image(self, image_path):
    """显示图像"""
    try:
        if not os.path.exists(image_path):
            # 如果图像文件不存在,显示占位符
            self.image_label.config(text="暂无插画\n请添加相关图片到assets/images目录", 
                                  font=('微软雅黑', 14), fg='gray')
            return
        
        # 加载并显示图像
        image = Image.open(image_path)
        # 调整图像大小以适应窗口
        image.thumbnail((400, 300), Image.Resampling.LANCZOS)
        photo = ImageTk.PhotoImage(image)
        
        self.image_label.config(image=photo, text="")
        self.image_label.image = photo  # 保持引用
        
    except Exception as e:
        print(f"显示图像时出错: {e}")
        self.image_label.config(text="图像加载失败", font=('微软雅黑', 14), fg='red')

class ControlPanel(tk.Frame): """控制面板组件""" def init(self, parent, player_callback, **kwargs): super().init(parent, **kwargs)

    self.player_callback = player_callback
    self.setup_ui()

def setup_ui(self):
    # 播放控制按钮
    button_frame = tk.Frame(self, bg=COLORS['background'])
    button_frame.pack(pady=10)
    
    # 播放按钮
    self.play_button = tk.Button(
        button_frame,
        text="▶ 播放",
        font=('微软雅黑', 12, 'bold'),
        bg=COLORS['primary'],
        fg='white',
        padx=20,
        pady=5,
        command=self.on_play
    )
    self.play_button.pack(side=tk.LEFT, padx=5)
    
    # 暂停按钮
    self.pause_button = tk.Button(
        button_frame,
        text="⏸ 暂停",
        font=('微软雅黑', 12),
        bg=COLORS['secondary'],
        fg='white',
        padx=20,
        pady=5,
        command=self.on_pause
    )
    self.pause_button.pack(side=tk.LEFT, padx=5)
    
    # 停止按钮
    self.stop_button = tk.Button(
        button_frame,
        text="⏹ 停止",
        font=('微软雅黑', 12),
        bg=COLORS['accent'],
        fg='white',
        padx=20,
        pady=5,
        command=self.on_stop
    )
    self.stop_button.pack(side=tk.LEFT, padx=5)
    
    # 音量控制
    volume_frame = tk.Frame(self, bg=COLORS['background'])
    volume_frame.pack(pady=10)
    
    tk.Label(volume_frame, text="音量:", font=('微软雅黑', 10), 
            bg=COLORS['background']).pack(side=tk.LEFT)
    
    self.volume_scale = tk.Scale(
        volume_frame,
        from_=0,
        to=100,
        orient=tk.HORIZONTAL,
        command=self.on_volume_change
    )
    self.volume_scale.set(70)
    self.volume_scale.pack(side=tk.LEFT, padx=10)

def on_play(self):
    if self.player_callback:
        self.player_callback('play')

def on_pause(self):
    if self.player_callback:
        self.player_callback('pause')

def on_stop(self):
    if self.player_callback:
        self.player_callback('stop')

def on_volume_change(self, value):
    if self.player_callback:
        self.player_callback('volume', int(value) / 100)

5. 主程序 (main.py)

""" 主程序 - 民间童谣播放器主界面 """

import tkinter as tk from tkinter import ttk import os import sys from config import * from data_manager import FolkRhymeDataManager from audio_player import AudioPlayer from ui_components import RhymeCard, LyricDisplay, ImageViewer, ControlPanel

class FolkRhymePlayer: def init(self): self.root = tk.Tk() self.root.title(f"{APP_NAME} v{VERSION}") self.root.geometry(f"{WINDOW_SIZE[0]}x{WINDOW_SIZE[1]}") self.root.configure(bg=COLORS['background'])

    # 初始化组件
    self.data_manager = FolkRhymeDataManager()
    self.audio_player = AudioPlayer()
    self.current_rhyme = None
    
    # 确保资源目录存在
    self.create_directories()
    
    # 设置UI
    self.setup_ui()
    
    # 绑定关闭事件
    self.root.protocol("WM_DELETE_WINDOW", self.on_closing)

def create_directories(self):
    """创建必要的目录"""
    directories = [ASSETS_DIR, IMAGES_DIR, AUDIO_DIR]
    for directory in directories:
        if not os.path.exists(directory):
            os.makedirs(directory)

def setup_ui(self):
    """设置用户界面"""
    # 创建主框架
    main_frame = tk.Frame(self.root, bg=COLORS['background'])
    main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
    
    # 顶部搜索栏
    self.setup_search_bar(main_frame)
    
    # 创建左右分栏
    paned_window = ttk.PanedWindow(main_frame, orient=tk.HORIZONTAL)
    paned_window.pack(fill=tk.BOTH, expand=True, pady=10)
    
    # 左侧面板 - 地域选择和童谣列表
    left_frame = self.setup_left_panel(paned_window)
    paned_window.add(left_frame, weight=1)
    
    # 右侧面板 - 歌词、图像和控制
    right_frame = self.setup_right_panel(paned_window)
    paned_window.add(right_frame, weight=1)

def setup_search_bar(self, parent):
    """设置搜索栏"""
    search_frame = tk.Frame(parent, bg=COLORS['background'])
    search_frame.pack(fill=tk.X, pady=(0, 10))
    
    tk.Label(search_frame, text="搜索童谣:", font=('微软雅黑', 10), 
            bg=COLORS['background']).pack(side=tk.LEFT)
    
    self.search_var = tk.StringVar()
    search_entry = tk.Entry(search_frame, textvariable=self.search_var, width=30)
    search_entry.pack(side=tk.LEFT, padx=5)
    search_entry.bind('<Return>', self.on_search)
    
    search_button = tk.Button(search_frame, text="搜索", 
                             command=self.on_search,
                             bg=COLORS['accent'], fg='white')
    search_button.pack(side=tk.LEFT, padx=5)

def setup_left_panel(self, parent):
    """设置左侧面板"""
    left_frame = tk.Frame(parent, bg=COLORS['background'])
    
    # 地域选择
    region_frame = tk.LabelFrame(left_frame, text="选择地域", 
                               font=('微软雅黑', 12, 'bold'),
                               bg=COLORS['background'], fg=COLORS['text'])
    region_frame.pack(fill=tk.X, pady=(0, 10))
    
    self.region_var = tk.StringVar(value=REGIONS[0])
    for region in REGIONS:
        rb = tk.Radiobutton(region_frame, text=region, variable=self.region_var,
                           value=region, command=self.on_region_change,
                           bg=COLORS['background'], font=('微软雅黑', 10))
        rb.pack(anchor=tk.W, padx=10, pady=2)
    
    # 童谣列表
    list_frame = tk.LabelFrame(left_frame, text="童谣列表", 
                              font=('微软雅黑', 12, 'bold'),
                              bg=COLORS['background'], fg=COLORS['text'])
    list_frame.pack(fill=tk.BOTH, expand=True)
    
    # 创建滚动框架
    canvas = tk.Canvas(list_frame, bg=COLORS['background'])
    scrollbar = ttk.Scrollbar(list_frame, orient="vertical", command=canvas.yview)
    self.scrollable_frame = tk.Frame(canvas, bg=COLORS['background'])
    
    self.scrollable_frame.bind(
        "<Configure>",
        lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
    )
    
    canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
    canvas.configure(yscrollcommand=scrollbar.set)
    
    canvas.pack(side="left", fill="both", expand=True)
    scrollbar.pack(side="right", fill="y")
    
    # 初始加载童谣列表
    self.load_rhyme_list()
    
    return left_frame

def setup_right_panel(self, parent):
    """设置右侧面板"""
    right_frame = tk.Frame(parent, bg=COLORS['background'])
    
    # 歌词显示
    self.lyric_display = LyricDisplay(right_frame)
    self.lyric_display.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
    
    # 图像查看器
    self.image_viewer = ImageViewer(right_frame)
    self.image_viewer.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
    
    # 控制面板
    self.control_panel = ControlPanel(right_frame, self.player_control_callback)
    self.control_panel.pack(fill=tk.X)
    
    return right_frame

def load_rhyme_list(self):
    """加载童谣列表"""
    # 清空现有列表
    for widget in self.scrollable_frame.winfo_children():
        widget.destroy()
    
    # 获取当前选择的地域
    current_region = self.region_var.get()
    rhymes = self.data_manager.get_rhymes_by_region(current_region)
    
    if not rhymes:
        no_data_label = tk.Label(self.scrollable_frame, 
                               text=f"暂无{current_region}地区的童谣数据",
                               font=('微软雅黑', 12),
                               bg=COLORS['background'], fg='gray')
        no_data_label.pack(pady=20)
        return
    
    # 创建童谣卡片
    for rhyme in rhymes:
        card = RhymeCard(
            self.scrollable_frame,
            rhyme,
            command=self.on_rhyme_select,
            width=250,
            height=120
        )

关注我,有更多实用程序等着你!