第十三课:music-list(6-8)

本文介绍了一个基于Vue的音乐列表组件实现过程,包括组件props配置、动态样式展示、事件监听及响应式数据处理等核心内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.music-list.vue
props 接收的数据

 props: {
      bgImage: {
        type: String,
        default: ''
      },
      songs: {
        type: Array,
        default: []
      },
      title: {
        type: String,
        default: ''
      },
      rank: {
        type: Boolean,
        default: false
      }
    },

2.使用music-list组件

 <music-list :title="title" :bg-image="bgImage" :songs="songs"></music-list>

  import MusicList from 'components/music-list/music-list'

在计算属性里面

      title() {
        return this.singer.name
      },
      bgImage() {
        return this.singer.avatar
      },

3.抽象出songList组件

<template>
  <div class="song-list">
    <ul>
      <li @click="selectItem(song, index)" class="item" v-for="(song, index) in songs">
        <div class="rank" v-show="rank">
          <span :class="getRankCls(index)" v-text="getRankText(index)"></span>
        </div>
        <div class="content">
          <h2 class="name">{{song.name}}</h2>
          <p class="desc">{{getDesc(song)}}</p>
        </div>
      </li>
    </ul>
  </div>
</template>


 methods: {
      selectItem(item, index) {
        this.$emit('select', item, index)
      },
      getDesc(song) {
        return `${song.singer}·${song.album}`
      },
      getRankCls(index) {
        if (index <= 2) {
          return `icon icon${index}`
        } else {
          return 'text'
        }
      },
      getRankText(index) {
        if (index > 2) {
          return index + 1
        }
      }
    }

music-list
1.应用Bsroll
2.应用SongList

 <scroll :data="songs" @scroll="scroll"
            :listen-scroll="listenScroll" :probe-type="probeType" class="list" ref="list">
      <div class="song-list-wrapper">
        <song-list :songs="songs" :rank="rank" @select="selectItem"></song-list>
      </div>
      <div v-show="!songs.length" class="loading-container">
        <loading></loading>
      </div>
    </scroll>

此时头像不显示

 <div class="bg-image" :style="bgStyle" ref="bgImage">
      <div class="play-wrapper">
        <div ref="playBtn" v-show="songs.length>0" class="play" @click="random">
          <i class="icon-play"></i>
          <span class="text">随机播放全部</span>
        </div>
      </div>
      <div class="filter" ref="filter"></div>
    </div>

 mounted() {

      this.$refs.list.$el.style.top = `${this.imageHeight}px`
    },
import json import requests import os import time from datetime import datetime, timedelta import pytz from mutagen.mp3 import MP3 from mutagen.mp4 import MP4 import subprocess import platform def get_broadcast_data_with_token(tenant_access_token): """ 使用新的 token 获取飞书的数据 """ # 获取 Feishu Bitable 数据 url = 'https://open.feishu.cn/open-apis/bitable/v1/apps/E1zybPqiqa0TaesZjKKch5ZcnJd/tables/tblwFY4k3pmrV5WK/records/search' headers = { 'Content-Type': 'application/json', 'Authorization': f'Bearer {tenant_access_token}' # 使用新的 token } data = {} # 如果需要传递查询条件,可以在这里添加 try: response = requests.post(url, headers=headers, json=data) response.raise_for_status() # 如果响应失败,将抛出异常 response_dict = response.json() # 将返回的 JSON 数据转换为字典 items = response_dict.get("data", {}).get("items", []) data = [] for item in items: fields = item.get("fields", {}) data.append({ "播音日期": extract_broadcast_date(fields, '播音日期'), "时间段": extract_time_segment(fields, '时间段'), "开播音乐file_token": extract_file_token(fields, '开播音乐'), "开场白-播报file_token": extract_file_token(fields, '开场白-播报'), "需更新文案-播报file_token": extract_file_token(fields, '需更新文案-播报'), "壹首歌file_token": extract_file_token(fields, '壹首歌'), "需更新文案2-播报file_token": extract_file_token(fields, '需更新文案2-播报'), "贰首歌file_token": extract_file_token(fields, '贰首歌'), "结束语-播报file_token": extract_file_token(fields, '结束语-播报'), "结束音乐file_token": extract_file_token(fields, '结束音乐') }) return data except requests.exceptions.HTTPError as http_err: print(f"HTTP 错误发生: {http_err}") except Exception as err: print(f"其他错误发生: {err}") return [] def extract_file_token(fields, field_name): """提取 file_token""" field_data = fields.get(field_name, []) if isinstance(field_data, list) and len(field_data) > 0: value = field_data[0] if isinstance(value, dict): return value.get("file_token", "") return '' def extract_time_segment(fields, field_name): """提取时间段字段""" field_data = fields.get(field_name, []) if isinstance(field_data, list) and len(field_data) > 0: value = field_data[0] if isinstance(value, dict): return value.get("text", "") return None def extract_broadcast_date(fields, field_name): """提取播音日期字段""" field_data = fields.get(field_name, 0) if isinstance(field_data, int): try: timestamp = field_data / 1000 # 时间戳转化为秒 parsed_date = datetime.fromtimestamp(timestamp, tz=pytz.utc).astimezone(pytz.timezone('Asia/Shanghai')) return parsed_date.strftime("%Y-%m-%d") # 转换为 "YYYY-MM-DD" 格式 except (ValueError, OverflowError): pass return None def get_auth_token(): """获取认证 token""" url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" headers = {"Content-Type": "application/json; charset=utf-8"} payload = {"app_id": "cli_a882683e8779d00c", "app_secret": "3NKkALA7vyMRVnpKJinmrb1LJ7YuK4H0"} try: response = requests.post(url, json=payload, headers=headers) response.raise_for_status() data = response.json() if data["code"] == 0: return data["tenant_access_token"] else: print(f"请求失败:{data['msg']}(错误码:{data['code']})") except requests.exceptions.RequestException as e: print(f"请求异常:{e}") return None def create_folder(folder_name): """创建文件夹""" if not os.path.exists(folder_name): os.makedirs(folder_name) def download_file(file_token, save_path, authorization): """下载文件""" url = f"https://open.feishu.cn/open-apis/drive/v1/medias/{file_token}/download" headers = {"Authorization": "Bearer " + authorization} try: response = requests.get(url, headers=headers, stream=True) if response.status_code == 200: with open(save_path, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) print(f"文件已成功下载到: {save_path}") return True else: print(f"请求失败,状态码: {response.status_code}") print(f"错误信息: {response.text}") except Exception as e: print(f"发生异常: {str(e)}") return False def get_audio_duration(file_path): """获取音频时长""" try: if file_path.endswith(".mp3"): audio = MP3(file_path) elif file_path.endswith(".mp4"): audio = MP4(file_path) else: print(f"不支持的文件格式: {file_path}") return 0 return audio.info.length except Exception as e: print(f"获取音频时长失败: {e}") return 0 def kill_previous_players(): """清理之前残留的播放器进程""" system = platform.system() # 获取当前操作系统类型 try: if system == "Windows": subprocess.run(['taskkill', '/F', '/IM', 'wmplayer.exe'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) subprocess.run(['taskkill', '/F', '/IM', 'vlc.exe'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) elif system == "Darwin": # macOS subprocess.run(['killall', 'afplay'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) elif system == "Linux": subprocess.run(['pkill', 'mpg123'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) print("已清理之前残留的播放器进程") except Exception as e: print(f"清理播放器进程时发生错误: {e}") def play_music_in_folder(folder_path): """播放文件夹中的音频文件,并在播放完成后关闭播放器""" audio_files = [f for f in os.listdir(folder_path) if f.endswith((".mp3", ".mp4"))] processes = [] # 用于存储播放器进程 for file_name in audio_files: full_file_path = os.path.join(folder_path, file_name) try: duration = get_audio_duration(full_file_path) if duration <= 0: print(f"无法获取 {file_name} 的时长,跳过播放") continue print(f"播放 {file_name},预计播放时长:{duration} 秒") if os.name == 'nt': # Windows 系统 process = subprocess.Popen(['start', '', full_file_path], shell=True) elif os.name == 'posix': # MacOS 或 Linux 系统 process = subprocess.Popen(['afplay', full_file_path]) # 对于 MacOS 使用 afplay else: print(f"不支持的操作系统类型: {os.name}") continue processes.append(process) # 保存进程对象 time.sleep(duration) # 等待音频播放完成 except Exception as e: print(f"无法播放 {full_file_path}: {e}") # 关闭所有播放器进程 for process in processes: try: if process.poll() is None: # 检查进程是否仍在运行 process.kill() # 强制终止进程 print("播放器已强制关闭") except Exception as e: print(f"关闭播放器失败: {e}") def process_time_segment(segment, download_offset, play_offset, data, authorization, folder_name): """处理一个时间段的下载和播放""" target_data = next((entry for entry in data if entry["时间段"] == segment), None) if not target_data: print(f"未找到时间段 {segment} 的文件数据!") return current_time = datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%H:%M') segment_start_time = segment.split("-")[0] # 如果当前时间已经超过该时间段的开始时间,则跳过 if current_time > segment_start_time: print(f"当前时间已超过时间段 {segment} 的开始时间,跳过该时间段的处理") return # 等待到达下载时间 download_time = (datetime.strptime(segment_start_time, "%H:%M") - timedelta(minutes=download_offset)).strftime("%H:%M") while current_time != download_time: current_time = datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%H:%M') time.sleep(1) # 在下载前3分钟,重新获取新的 token new_authorization = get_auth_token() if not new_authorization: print("获取新的 token 失败,无法继续执行操作。") return print(f"使用新的 token 开始获取 {segment} 的数据") # 使用新的 token 获取飞书数据 new_data = get_broadcast_data_with_token(new_authorization) if not new_data: print("获取新的飞书数据失败!") return # 获取当前时间段的数据 target_data = next((entry for entry in new_data if entry["时间段"] == segment), None) if not target_data: print(f"未找到时间段 {segment} 的文件数据!") return print(f"开始下载 {segment} 的文件") files = [] file_tokens = [ ("开播音乐file_token", "mp3"), ("开场白-播报file_token", "mp4"), ("需更新文案-播报file_token", "mp4"), ("壹首歌file_token", "mp3"), ("需更新文案2-播报file_token", "mp4"), ("贰首歌file_token", "mp3"), ("结束语-播报file_token", "mp4"), ("结束音乐file_token", "mp3") ] for i, (key, file_format) in enumerate(file_tokens): token = target_data.get(key) if token: save_path = os.path.join(folder_name, f"file_{i+1}.{file_format}") if download_file(token, save_path, new_authorization): files.append(save_path) # 清理之前残留的播放器进程(调整到这里) kill_previous_players() # 等待到达播放时间 play_time = (datetime.strptime(segment_start_time, "%H:%M") - timedelta(minutes=play_offset)).strftime("%H:%M") while current_time != play_time: current_time = datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%H:%M') time.sleep(1) print(f"开始播放 {segment} 的文件") play_music_in_folder(folder_name) # 播放结束后再次清理播放器进程 kill_previous_players() # 删除下载的文件 for file in files: os.remove(file) print(f"已删除文件: {file}") def main(): """主函数""" authorization = get_auth_token() if not authorization: return # Get the broadcast data using the authorization token data = get_broadcast_data_with_token(authorization) if not data: print("未获取到有效的数据!") return folder_name = "bobao" create_folder(folder_name) segments = [ ("08:10-08:15", 10, 0), # 提前10分钟下载文件,0-准点播放 ("10:30-10:40", 10, 0), ("13:00-13:10", 10, 0), ("15:00-15:10", 10, 0) ] for segment, download_offset, play_offset in segments: process_time_segment(segment, download_offset, play_offset, data, authorization, folder_name) # 主程序结束后,再清理一次播放器进程 kill_previous_players() if __name__ == "__main__": main() 上述代码早上7点55运行, ("08:10-08:15", 10, 0), ("10:30-10:40", 10, 0),这两个时间段正常运行,但是到了("13:00-13:10", 10, 0)时间段没有运行
最新发布
06-12
<think>我们注意到在main函数中,我们定义了四个时间段:segments=[("08:10-08:15",10,0),#提前10分钟下载文件,0-准点播放("10:30-10:40",10,0),("13:00-13:10",10,0),("15:00-15:10",10,0)]然后我们循环处理这些时间段。问题描述中提到前两个时间段(08:10-08:15和10:30-10:40)正常运行,但13:00-13:10这个时间段没有运行。可能的原因:1.时间判断逻辑问题:在`process_time_segment`函数中,我们首先检查当前时间是否已经超过时间段的开始时间(即`segment_start_time`),如果超过则跳过。如果程序在处理13:00这个时间段时,当前时间已经超过了13:00,那么就会跳过。2.程序运行时间过长:由于程序是顺序执行每个时间段的处理,如果前一个时间段的处理(比如10:30-10:40)在10:40之后没有结束,而是持续运行到了超过13:00,那么当轮到处理13:00这个时间段时,当前时间已经超过13:00,就会被跳过。3.数据获取问题:在每次处理一个时间段之前,我们会重新获取飞书数据(使用新的token),可能13:00这个时间段在飞书数据中没有找到。4.其他异常导致程序终止。我们重点分析第一个和第二个原因。查看代码,在`process_time_segment`函数中,有以下关键步骤:-首先,根据传入的`data`(这是最开始获取的飞书数据)查找当前时间段的数据。如果找不到,则返回。-然后,获取当前时间(上海时区),并提取出时间字符串(如"13:00")。-接着,判断当前时间是否已经超过时间段的开始时间(`segment_start_time`,即时间段字符串的第一个时间,如13:00-13:10中的"13:00")。如果超过,则跳过这个时间段。-如果没有超过,则等待到达下载时间(即时间段开始时间减去`download_offset`分钟)。下载时间设置为提前10分钟(因为传入的`download_offset`是10)。-到达下载时间后,重新获取新的token和新的飞书数据(注意:这里重新获取了飞书数据,所以可能和最初的数据不同)。-然后从新获取的飞书数据中再次查找当前时间段的数据(因为数据可能有更新)。-然后下载文件,等待播放时间(时间段开始时间减去`play_offset`分钟,这里`play_offset`为0,所以就是准时在13:00播放)。-播放结束后删除文件。现在,考虑程序处理完第二个时间段(10:30-10:40)后,程序会继续循环到第三个时间段(13:00-13:10)。但是在处理第三个时间段时,程序首先检查当前时间是否已经超过13:00。如果程序在10:40之后一直运行,那么在10:40到13:00之间,当前时间肯定是在13:00之前,所以不会因为超过开始时间而跳过。但是,当程序开始处理第三个时间段的时候,当前时间是多少?因为程序是顺序执行,而且每个时间段的处理都是在等待特定的时间点(下载时间和播放时间)。但是,在等待下一个时间段(13:00)的下载时间(即12:50)之前,程序会一直处于等待状态吗?实际上,在循环中,处理完一个时间段后,才会进入下一个时间段。所以,当处理第二个时间段(10:30-10:40)时,它会在10:40结束播放并删除文件,然后循环到第三个时间段(13:00-13:10)。在第三个时间段的处理函数中,我们立即检查当前时间是否超过13:00,如果当前时间是10:40(远早于13:00),那么不会跳过,然后程序会立即开始等待12:50(因为下载提前10分钟)吗?是的。因此,理论上,程序会在10:40处理完第二个时间段后,立即进入第三个时间段处理,然后一直等待到12:50(下载时间)。所以不会因为超过开始时间而跳过。那么,为什么第三个时间段没有运行?可能的原因:1.在等待过程中,程序出现了异常退出了。2.在等待过程中,系统休眠或时间同步问题导致等待条件未触发。3.在重新获取飞书数据时,没有获取到13:00时间段的数据。我们可以在代码中添加一些日志,记录每个时间段的处理开始和等待过程,以及重新获取数据后的结果。另外,我们注意到在`process_time_segment`函数中,我们重新获取了飞书数据(在下载时间点),并且用这个新数据去查找当前时间段的数据。可能在这个时间点(12:50)获取的数据中没有13:00时间段的数据。这可能是因为:-飞书数据中确实没有13:00时间段的数据(可能表格中没有这个时间段的数据,或者字段名不匹配)-获取数据失败(如网络问题,token失效等)为了解决这个问题,我们可以:1.在代码的关键步骤增加日志记录,比如记录重新获取到的数据,以及查找时间段的结果。2.考虑程序长时间运行的稳定性,确保不会因为某个异常而终止。针对目前的代码,我们进行以下调整:1.在`process_time_segment`函数中,增加详细的日志输出,包括:-进入处理函数的时间-检查当前时间与时间段开始时间的关系-在重新获取数据后,打印获取到的数据条数以及查找到的当前时间段的数据详情(如果找不到,打印错误)2.在`main`函数中使用try-except捕获异常,确保一个时间段的异常不会导致整个程序终止。由于我们无法直接运行并调试,我们首先修改代码,添加详细日志。同时,我们也要确保程序在等待过程中不会被系统休眠等因素干扰(这需要系统环境保证)。此外,我们还要考虑:程序在处理第二个时间段后,到第三个时间段的下载时间(12:50)之间,有2个多小时的空闲时间。在这段时间内,程序只是等待,但可能因为系统原因(如休眠)导致程序挂起,或者网络断开导致后续获取数据失败。因此,我们修改代码,在等待过程中使用小时间隔轮询,并记录等待状态。具体修改如下:修改`process_time_segment`函数中的等待部分:将:whilecurrent_time!=download_time:current_time=...time.sleep(1)改为每10秒打印一次等待状态(避免输出过于频繁,也可以避免长时间无输出被误判为卡死)。另外,我们在`main`函数的循环中捕获异常,并记录。修改后的`main`函数循环部分:```pythonforsegment,download_offset,play_offsetinsegments:try:process_time_segment(segment,download_offset,play_offset,data,authorization,folder_name)exceptExceptionase:print(f"处理时间段{segment}时发生异常:{e}")#可以选择记录日志或继续下一个时间段```在`process_time_segment`中,我们增加日志:```pythonprint(f"开始处理时间段:{segment},当前时间:{current_time}")#检查是否已经超过开始时间ifcurrent_time>segment_start_time:print(f"当前时间已超过时间段{segment}的开始时间,跳过该时间段的处理")returnprint(f"等待下载时间:{download_time}(提前{download_offset}分钟)")#等待到达下载时间whilecurrent_time< download_time:#我们使用小于,这样在未到时间时等待current_time=datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%H:%M')#每10秒打印一次time.sleep(10)#每10秒打印一次等待状态ifint(datetime.now().second)%10==0:print(f"当前时间:{current_time},等待下载时间:{download_time}")#上面的等待条件改为:当当前时间小于下载时间时等待,一旦当前时间大于等于下载时间,就执行下载#注意:我们之前使用的是等于,这可能会因为跳过而永远等不到(比如从10:40等到12:50,中间程序卡了一下,当前时间变成了12:51,则永远不会等于12:50)#所以,我们需要改变等待逻辑:改为等待到下载时间点及以后。但是,由于我们要提前10分钟下载,所以不能晚于下载时间点。#因此,我们最好使用时间比较(当前时间<下载时间)就等待,一旦当前时间>=下载时间,就跳出循环。#因此,我们将等待条件改为:#whiledatetime.now(pytz.timezone('Asia/Shanghai')).strftime('%H:%M')< download_time:#sleep(10)#注意:由于我们上面已经将current_time定义为字符串,字符串比较时间必须保证格式一致(都是%H:%M),并且字符串比较是按字典序,所以在时间格式中(小时两位数,分钟两位数)是可以直接比较的。#但是,原来的写法(等于)有风险,比如等待过程中错过了下载时间,就会一直等待。所以我们改用小于,然后当当前时间大于等于下载时间时,就执行下载。#所以,调整等待循环为:#whilecurrent_time< download_time:#sleep(10)#current_time=...#但是这样修改后,我们可能会提前几秒(但不会超过10秒)开始下载,是可以接受的。#另外,播放时间的等待同理修改。#因此,我们重新编写下载时间和播放时间的等待循环:#等待下载时间:whileTrue:current_time=datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%H:%M')ifcurrent_time>=download_time:breakprint(f"等待下载时间:{download_time},当前时间:{current_time}(还剩{calculate_time_difference(current_time,download_time)}秒)")time.sleep(10)#每10秒检查一次#同理,等待播放时间:whileTrue:current_time=datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%H:%M')ifcurrent_time>=play_time:breakprint(f"等待播放时间:{play_time},当前时间:{current_time}(还剩{calculate_time_difference(current_time,play_time)}秒)")time.sleep(10)#每10秒检查一次#注意:这里我们假设download_time和play_time都是%H:%M格式的字符串,并且当前时间也是同一格式,且同一时区。```其中,我们需要一个辅助函数`calculate_time_difference`来计算两个时间字符串(HH:MM)之间相差的秒数。添加这个函数:```pythondefcalculate_time_difference(time_str1,time_str2):"""计算两个时间字符串(HH:MM)之间的时间差(秒数),假设time_str2比time_str1晚"""t1=datetime.strptime(time_str1,"%H:%M")t2=datetime.strptime(time_str2,"%H:%M")ift2< t1:#如果time_str2比time_str1小,说明time_str2是第二天的时间t2=t2.replace(day=t1.day+1)else:t2=t2.replace(day=t1.day)t1=t1.replace(day=t1.day)delta=t2-t1returndelta.total_seconds()```这个函数用于计算两个时间相差的秒数,用于显示等待时间。但是,注意:我们等待的时间可能跨天,所以需要特殊处理。另外,由于我们的时间段都是同一天内的,并且程序会在当天运行,所以我们可以假设同一天比较。但是,如果等待时间跨过24点,那么我们的时间段定义本身也没有跨天,所以实际上不会发生。因此,我们简化:如果time_str2小于time_str1,则加上24小时再计算。```pythondefcalculate_time_difference(time_str1,time_str2):"""计算两个时间字符串(HH:MM)之间的时间差(秒数)"""t1=datetime.strptime(time_str1,"%H:%M")t2=datetime.strptime(time_str2,"%H:%M")#计算时间差(秒数)delta=(t2-t1).total_seconds()ifdelta<0:#如果为负数,说明t2是第二天的delta+=24*3600returnabs(delta)```不过,我们的等待逻辑是:只要当前时间(字符串)>=目标时间(字符串)就结束。字符串比较是按字典序,而时间字符串的字典序和时间顺序一致(因为小时和分钟都是两位),所以可以直接用字符串比较。因此,我们使用字符串比较等待,并计算距离目标时间的秒数来显示剩余时间。但是,为了避免跨天的问题,我们直接使用时间戳计算时间差:```pythondefcalculate_time_difference(current,target):"""计算从当前时间(字符串'HH:MM')到目标时间(字符串'HH:MM')的秒数"""#获取当前日期时间now=datetime.now(pytz.timezone('Asia/Shanghai'))#解析目标时间target_hour,target_minute=map(int,target.split(':'))#构建目标日期时间(假设为今天)target_time=now.replace(hour=target_hour,minute=target_minute,second=0,microsecond=0)#如果目标时间已经过去(即比现在早),则加一天iftarget_time< now:target_time+=timedelta(days=1)#计算时间差(秒数)return(target_time-now).total_seconds()```这个函数更准确,但注意我们等待循环中每10秒检测一次,所以不需要非常精确的秒数,我们只是用来显示。由于我们等待循环中已经简化成字符串比较,所以这个函数可能有点重。我们可以选择不显示具体的秒数,而只显示时间点。因此,我们可以先不添加这个函数,而是只打印当前时间和目标时间。由于修改代码较多,我们提供一个修改后的`process_time_segment`函数版本(只修改了等待部分):```pythondefprocess_time_segment(segment,download_offset,play_offset,data,authorization,folder_name):"""处理一个时间段的下载和播放"""print(f"\n====开始处理时间段:{segment}====")target_data=next((entryforentryindataifentry["时间段"]==segment),None)ifnottarget_data:print(f"在初始数据中未找到时间段{segment}的文件数据!")returncurrent_time=datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%H:%M')segment_start_time=segment.split("-")[0]#例如"13:00"print(f"当前时间:{current_time},时间段的开始时间:{segment_start_time}")#如果当前时间已经超过该时间段的开始时间,则跳过ifcurrent_time>segment_start_time:print(f"当前时间已超过时间段{segment}的开始时间,跳过该时间段的处理")return#计算下载时间:开始时间往前推download_offset分钟download_time=(datetime.strptime(segment_start_time,"%H:%M")-timedelta(minutes=download_offset))download_time=download_time.strftime("%H:%M")print(f"计算下载时间:{download_time}(提前{download_offset}分钟)")#等待到达下载时间print(f"等待下载时间:{download_time}...")whileTrue:current_time=datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%H:%M')ifcurrent_time>=download_time:breaktime.sleep(10)#每10秒检查一次print(f"当前时间:{current_time},目标下载时间:{download_time}")print(f"已到达下载时间:{current_time}")#在下载前3分钟,重新获取新的token(但实际上我们已经在下载时间点,所以重新获取token)new_authorization=get_auth_token()ifnotnew_authorization:print("获取新的token失败,无法继续执行操作。")returnprint(f"使用新的token开始获取{segment}的数据")#使用新的token获取飞书数据new_data=get_broadcast_data_with_token(new_authorization)ifnotnew_data:print("获取新的飞书数据失败!")return#获取当前时间段的数据target_data=next((entryforentryinnew_dataifentry["时间段"]==segment),None)ifnottarget_data:print(f"在新数据中未找到时间段{segment}的文件数据!")return#...后面的下载和播放部分不变...#等待播放时间play_time=segment_start_time#因为play_offset为0print(f"等待播放时间:{play_time}...")whileTrue:current_time=datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%H:%M')ifcurrent_time>=play_time:breaktime.sleep(10)print(f"当前时间:{current_time},目标播放时间:{play_time}")print(f"已到达播放时间:{current_time},开始播放...")#...播放...```注意:我们调整了等待逻辑,使用“>=”来判断,并且每10秒检查一次,避免CPU占用过高,同时输出日志表明程序在运行。另外,由于等待下载时间可能需要数小时,期间每隔10秒打印一次,这样日志会很多,可以考虑增加等待间隔到1分钟,并且每分钟打印一次。我们可以修改等待循环中的sleep时间为60秒,并且每分钟打印一次。但是,为了更精确,我们可以在循环中计算距离目标时间的分钟数,然后动态调整sleep时间(比如小于10分钟时每秒检查一次,大于10分钟时每分钟检查一次)。但为了简化,我们暂时使用60秒的检查间隔,并且每分钟打印一次。修改等待循环:```pythonwait_interval=60#60秒检查一次#等待下载时间print(f"等待下载时间:{download_time}...")whileTrue:current_time=datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%H:%M')ifcurrent_time>=download_time:break#打印一次后等待60秒print(f"当前时间:{current_time},目标下载时间:{download_time}")time.sleep(wait_interval)```播放等待同理。这样,我们就增加了程序的健壮性,并且通过日志可以追踪程序的等待状态。最后,我们修改`main`函数,捕获每个时间段的异常,确保一个时间段的异常不会影响下一个时间段。修改后的`main`函数循环:```pythonsegments=[("08:10-08:15",10,0),#提前10分钟下载文件,0-准点播放("10:30-10:40",10,0),("13:00-13:10",10,0),("15:00-15:10",10,0)]forsegment,download_offset,play_offsetinsegments:try:print(f"\n准备处理时间段:{segment}")process_time_segment(segment,download_offset,play_offset,data,authorization,folder_name)exceptExceptionase:importtracebackprint(f"处理时间段{segment}时发生异常:{e}")print(traceback.format_exc())```这样修改后,程序能够更好地运行,并且在出现异常时,我们可以从日志中查看原因。请根据这些修改调整代码,然后再次运行观察13:00时间段是否能够正常执行。注意:这些修改主要增加日志和改变等待机制,并没有改变核心逻辑。请确保在修改后测试所有时间段。由于修改代码较长,这里仅提供修改思路,具体代码请根据上述思路调整。如果问题依然存在,请检查重新获取飞书数据时,是否能够正确获取到13:00时间段的数据。另外,建议在本地运行或者在服务器上运行时,确保系统时间准确,并且程序运行期间不会休眠。</think> 我分析了你的代码,发现了可能导致下午时间段未能正常运行的原因。下面是完整的解决方案: ```python import json import requests import os import time from datetime import datetime, timedelta import pytz from mutagen.mp3 import MP3 from mutagen.mp4 import MP4 import subprocess import platform import traceback # 用于打印完整错误信息 def get_broadcast_data_with_token(tenant_access_token): """ 使用新的 token 获取飞书的数据 """ # 获取 Feishu Bitable 数据 url = 'https://open.feishu.cn/open-apis/bitable/v1/apps/E1zybPqiqa0TaesZjKKch5ZcnJd/tables/tblwFY4k3pmrV5WK/records/search' headers = { 'Content-Type': 'application/json', 'Authorization': f'Bearer {tenant_access_token}' # 使用新的 token } data = {} # 如果需要传递查询条件,可以在这里添加 try: response = requests.post(url, headers=headers, json=data) response.raise_for_status() # 如果响应失败,将抛出异常 response_dict = response.json() # 将返回的 JSON 数据转换为字典 items = response_dict.get("data", {}).get("items", []) data = [] for item in items: fields = item.get("fields", {}) data.append({ "播音日期": extract_broadcast_date(fields, '播音日期'), "时间段": extract_time_segment(fields, '时间段'), "开播音乐file_token": extract_file_token(fields, '开播音乐'), "开场白-播报file_token": extract_file_token(fields, '开场白-播报'), "需更新文案-播报file_token": extract_file_token(fields, '需更新文案-播报'), "壹首歌file_token": extract_file_token(fields, '壹首歌'), "需更新文案2-播报file_token": extract_file_token(fields, '需更新文案2-播报'), "贰首歌file_token": extract_file_token(fields, '贰首歌'), "结束语-播报file_token": extract_file_token(fields, '结束语-播报'), "结束音乐file_token": extract_file_token(fields, '结束音乐') }) return data except requests.exceptions.HTTPError as http_err: print(f"HTTP 错误发生: {http_err}") except Exception as err: print(f"其他错误发生: {err}") print(traceback.format_exc()) # 打印详细错误信息 return [] def extract_file_token(fields, field_name): """提取 file_token""" field_data = fields.get(field_name, []) if isinstance(field_data, list) and len(field_data) > 0: value = field_data[0] if isinstance(value, dict): return value.get("file_token", "") return '' def extract_time_segment(fields, field_name): """提取时间段字段""" field_data = fields.get(field_name, []) if isinstance(field_data, list) and len(field_data) > 0: value = field_data[0] if isinstance(value, dict): return value.get("text", "") return None def extract_broadcast_date(fields, field_name): """提取播音日期字段""" field_data = fields.get(field_name, 0) if isinstance(field_data, int): try: timestamp = field_data / 1000 # 时间戳转化为秒 parsed_date = datetime.fromtimestamp(timestamp, tz=pytz.utc).astimezone(pytz.timezone('Asia/Shanghai')) return parsed_date.strftime("%Y-%m-%d") # 转换为 "YYYY-MM-DD" 格式 except (ValueError, OverflowError): pass return None def get_auth_token(): """获取认证 token""" url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" headers = {"Content-Type": "application/json; charset=utf-8"} payload = {"app_id": "cli_a882683e8779d00c", "app_secret": "3NKkALA7vyMRVnpKJinmrb1LJ7YuK4H0"} try: response = requests.post(url, json=payload, headers=headers) response.raise_for_status() data = response.json() if data["code"] == 0: return data["tenant_access_token"] else: print(f"请求失败:{data['msg']}(错误码:{data['code']})") except requests.exceptions.RequestException as e: print(f"请求异常:{e}") print(traceback.format_exc()) return None def create_folder(folder_name): """创建文件夹""" if not os.path.exists(folder_name): os.makedirs(folder_name) def download_file(file_token, save_path, authorization): """下载文件""" url = f"https://open.feishu.cn/open-apis/drive/v1/medias/{file_token}/download" headers = {"Authorization": "Bearer " + authorization} try: response = requests.get(url, headers=headers, stream=True) if response.status_code == 200: with open(save_path, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) print(f"文件已成功下载到: {save_path}") return True else: print(f"请求失败,状态码: {response.status_code}") print(f"错误信息: {response.text}") except Exception as e: print(f"发生异常: {str(e)}") print(traceback.format_exc()) return False def get_audio_duration(file_path): """获取音频时长""" try: if file_path.endswith(".mp3"): audio = MP3(file_path) elif file_path.endswith(".mp4"): audio = MP4(file_path) else: print(f"不支持的文件格式: {file_path}") return 0 return audio.info.length except Exception as e: print(f"获取音频时长失败: {e}") print(traceback.format_exc()) return 0 def kill_previous_players(): """清理之前残留的播放器进程""" system = platform.system() # 获取当前操作系统类型 try: if system == "Windows": subprocess.run(['taskkill', '/F', '/IM', 'wmplayer.exe'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) subprocess.run(['taskkill', '/F', '/IM', 'vlc.exe'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) elif system == "Darwin": # macOS subprocess.run(['killall', 'afplay'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) elif system == "Linux": subprocess.run(['pkill', 'mpg123'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) print("已清理之前残留的播放器进程") except Exception as e: print(f"清理播放器进程时发生错误: {e}") print(traceback.format_exc()) def play_music_in_folder(folder_path): """播放文件夹中的音频文件,并在播放完成后关闭播放器""" audio_files = [f for f in os.listdir(folder_path) if f.endswith((".mp3", ".mp4"))] processes = [] # 用于存储播放器进程 # 按创建时间排序文件,确保播放顺序正确 audio_files = sorted(audio_files, key=lambda x: os.path.getctime(os.path.join(folder_path, x))) for file_name in audio_files: full_file_path = os.path.join(folder_path, file_name) try: duration = get_audio_duration(full_file_path) if duration <= 0: print(f"无法获取 {file_name} 的时长,跳过播放") continue print(f"播放 {file_name},预计播放时长:{duration} 秒") if os.name == 'nt': # Windows 系统 process = subprocess.Popen(['start', 'wmplayer', '/play', '/close', full_file_path], shell=True) elif os.name == 'posix': # MacOS 或 Linux 系统 process = subprocess.Popen(['afplay', full_file_path]) # 对于 MacOS 使用 afplay else: print(f"不支持的操作系统类型: {os.name}") continue processes.append(process) # 保存进程对象 time.sleep(duration) # 等待音频播放完成 except Exception as e: print(f"无法播放 {full_file_path}: {e}") print(traceback.format_exc()) # 关闭所有播放器进程 for process in processes: try: if process.poll() is None: # 检查进程是否仍在运行 process.terminate() # 尝试正常终止 time.sleep(2) # 等待2秒 if process.poll() is None: # 如果还在运行,强制终止 process.kill() # 强制终止进程 print("播放器已强制关闭") except Exception as e: print(f"关闭播放器失败: {e}") print(traceback.format_exc()) def process_time_segment(segment, download_offset, play_offset, data, folder_name): """处理一个时间段的下载和播放""" print(f"\n==== 开始处理时间段: {segment} ====") try: # 1. 获取当前时间(上海时区) tz_shanghai = pytz.timezone('Asia/Shanghai') current_time = datetime.now(tz_shanghai) print(f"当前时间: {current_time.strftime('%Y-%m-%d %H:%M:%S')}") # 2. 获取时间段中的开始时间 segment_start, segment_end = segment.split('-') start_hour, start_minute = map(int, segment_start.split(':')) # 创建今天的时间对象(上海时区) segment_start_time = tz_shanghai.localize( datetime(current_time.year, current_time.month, current_time.day, start_hour, start_minute) ) # 打印关键时间信息 print(f"开始时间: {segment_start_time.strftime('%Y-%m-%d %H:%M:%S')}") # 3. 如果当前时间已经超过该时间段的开始时间,则跳过 if current_time > segment_start_time: print(f"警告:当前时间已超过时间段 {segment} 的开始时间,跳过该时间段的处理") return # 4. 计算下载时间和播放时间 download_time = segment_start_time - timedelta(minutes=download_offset) play_time = segment_start_time - timedelta(minutes=play_offset) print(f"计划下载时间: {download_time.strftime('%Y-%m-%d %H:%M:%S')} (提前{download_offset}分钟)") print(f"计划播放时间: {play_time.strftime('%Y-%m-%d %H:%M:%S')} (提前{play_offset}分钟)") # 5. 等待到达下载时间 time_to_download = (download_time - current_time).total_seconds() if time_to_download > 0: print(f"距离下载时间还有 {time_to_download} 秒,等待中...") time.sleep(time_to_download) print(f"到达下载时间: {datetime.now(tz_shanghai).strftime('%Y-%m-%d %H:%M:%S')}") # 6. 重新获取token(避免token过期) print("正在获取新的飞书访问令牌...") new_authorization = get_auth_token() if not new_authorization: print("错误:获取新的 token 失败,无法继续执行操作。") return # 7. 使用新的 token 获取飞书数据 print("正在获取最新的飞书数据...") new_data = get_broadcast_data_with_token(new_authorization) if not new_data or len(new_data) == 0: print("错误:获取新的飞书数据失败!") return # 8. 获取当前时间段的数据 target_data = next((entry for entry in new_data if entry["时间段"] == segment), None) if not target_data: print(f"错误:未找到时间段 {segment} 的文件数据!") return print(f"找到时间段 {segment} 的数据,准备下载相关文件") # 9. 下载所需文件 files = [] file_tokens = [ ("开播音乐file_token", "mp3"), ("开场白-播报file_token", "mp4"), ("需更新文案-播报file_token", "mp4"), ("壹首歌file_token", "mp3"), ("需
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值