fix: clamp default audio bitrate to 160 kbps for iPod Nano 7 compatibility

This commit is contained in:
Maksim Totmin 2026-05-31 21:45:49 +07:00
parent 0f12c4985f
commit 9da689782a
5 changed files with 21 additions and 18 deletions

View File

@ -43,7 +43,7 @@ class AudioConverter:
track_info: TrackInfo, track_info: TrackInfo,
input_file: str, input_file: str,
format: str = "m4a", format: str = "m4a",
audio_bitrate: int = 256, audio_bitrate: int = 160,
overwrite: bool = False) -> str: overwrite: bool = False) -> str:
""" """
Convert audio file to iPod-compatible format Convert audio file to iPod-compatible format
@ -90,10 +90,13 @@ class AudioConverter:
logger.warning(f"Existing file is too small ({file_size} bytes), re-converting") logger.warning(f"Existing file is too small ({file_size} bytes), re-converting")
os.remove(output_file) os.remove(output_file)
# Build ffmpeg command — iPod Nano 7 is strict about AAC parameters: # iPod Nano 7 is strict about AAC parameters:
# - AAC-LC profile only (HE-AAC causes playback issues/crashes) # - AAC-LC profile only (HE-AAC causes playback issues/crashes)
# - 44100 Hz sample rate (firmware expects this exactly) # - 44100 Hz sample rate (firmware expects this exactly)
# - Max 160 kbps for stereo (higher bitrates may truncate playback) # - Max 160 kbps for stereo (higher bitrates cause clicks/truncation)
if audio_bitrate > 160:
logger.warning("Bitrate %d kbps exceeds iPod Nano 7 max (160 kbps), clamping to 160", audio_bitrate)
audio_bitrate = 160
codec = "aac" if format == "m4a" else "libmp3lame" codec = "aac" if format == "m4a" else "libmp3lame"
cmd = [ cmd = [
"ffmpeg", "-y", "-i", input_file, "ffmpeg", "-y", "-i", input_file,
@ -190,7 +193,7 @@ class AudioConverter:
output_file: Optional[str] = None, output_file: Optional[str] = None,
resolution: str = "640x480", resolution: str = "640x480",
video_bitrate: int = 1500, video_bitrate: int = 1500,
audio_bitrate: int = 256, audio_bitrate: int = 160,
overwrite: bool = False) -> str: overwrite: bool = False) -> str:
""" """
Convert video file to iPod-compatible format Convert video file to iPod-compatible format

View File

@ -74,8 +74,8 @@ class YouTubeToIPodCLI:
"--quality", "-q", "--quality", "-q",
help="Audio quality in kbps", help="Audio quality in kbps",
type=int, type=int,
choices=[128, 192, 256, 320], choices=[96, 112, 128, 160],
default=256 default=160
) )
# Video support # Video support
@ -128,7 +128,7 @@ class YouTubeToIPodCLI:
return parser.parse_args() return parser.parse_args()
def download(self, url: str, format: str = "m4a", quality: int = 256, video: bool = False) -> List[TrackInfo]: def download(self, url: str, format: str = "m4a", quality: int = 160, video: bool = False) -> List[TrackInfo]:
""" """
Download audio/video from YouTube Download audio/video from YouTube
@ -156,7 +156,7 @@ class YouTubeToIPodCLI:
tracks: List[TrackInfo], tracks: List[TrackInfo],
output_dir: str, output_dir: str,
format: str = "m4a", format: str = "m4a",
quality: int = 256, quality: int = 160,
video: bool = False, video: bool = False,
resolution: str = "640x480") -> List[Tuple[TrackInfo, str]]: resolution: str = "640x480") -> List[Tuple[TrackInfo, str]]:
""" """

View File

@ -97,7 +97,7 @@ class ConfigLoader:
self.config.set('General', 'output_dir', os.path.join(os.path.expanduser("~"), "Music", "iPod")) self.config.set('General', 'output_dir', os.path.join(os.path.expanduser("~"), "Music", "iPod"))
self.config.set('General', 'format', 'm4a') self.config.set('General', 'format', 'm4a')
self.config.set('General', 'quality', '256') self.config.set('General', 'quality', '160')
self.config.set('General', 'clean_temp', 'true') self.config.set('General', 'clean_temp', 'true')
# Video section # Video section

View File

@ -69,7 +69,7 @@ class WorkerThread(QThread):
url = self.kwargs.get("url") url = self.kwargs.get("url")
output_dir = self.kwargs.get("output_dir", "downloads") output_dir = self.kwargs.get("output_dir", "downloads")
fmt = self.kwargs.get("format", "m4a") fmt = self.kwargs.get("format", "m4a")
quality = self.kwargs.get("quality", 256) quality = self.kwargs.get("quality", 160)
self.progress_signal.emit(0, f"Initializing download from {url}") self.progress_signal.emit(0, f"Initializing download from {url}")
@ -93,7 +93,7 @@ class WorkerThread(QThread):
local_files = self.kwargs.get("local_files", []) local_files = self.kwargs.get("local_files", [])
output_dir = self.kwargs.get("output_dir", "converted") output_dir = self.kwargs.get("output_dir", "converted")
fmt = self.kwargs.get("format", "m4a") fmt = self.kwargs.get("format", "m4a")
quality = self.kwargs.get("quality", 256) quality = self.kwargs.get("quality", 160)
valid_tracks = [ valid_tracks = [
t for t in tracks t for t in tracks
@ -314,9 +314,9 @@ class MainWindow(QMainWindow):
self.format_combo.addItems(["m4a", "mp3"]) self.format_combo.addItems(["m4a", "mp3"])
quality_label = QLabel("Quality (kbps):") quality_label = QLabel("Quality (kbps):")
self.quality_spin = QSpinBox() self.quality_spin = QSpinBox()
self.quality_spin.setRange(128, 320) self.quality_spin.setRange(96, 160)
self.quality_spin.setValue(256) self.quality_spin.setValue(160)
self.quality_spin.setSingleStep(32) self.quality_spin.setSingleStep(16)
format_layout.addWidget(format_label) format_layout.addWidget(format_label)
format_layout.addWidget(self.format_combo) format_layout.addWidget(self.format_combo)
format_layout.addWidget(quality_label) format_layout.addWidget(quality_label)

View File

@ -215,7 +215,7 @@ class YouTubeDownloader:
logger.error(f"Error extracting playlist info: {e}") logger.error(f"Error extracting playlist info: {e}")
return [] return []
def download_audio(self, track_info: TrackInfo, format: str = "m4a", quality: int = 256) -> Optional[str]: def download_audio(self, track_info: TrackInfo, format: str = "m4a", quality: int = 160) -> Optional[str]:
""" """
Download audio from YouTube video Download audio from YouTube video
@ -341,7 +341,7 @@ class YouTubeDownloader:
if self.progress_callback: if self.progress_callback:
self.progress_callback(95, f"Download error: {self.current_track.title}") self.progress_callback(95, f"Download error: {self.current_track.title}")
def process_url(self, url: str, format: str = "m4a", quality: int = 256) -> List[TrackInfo]: def process_url(self, url: str, format: str = "m4a", quality: int = 160) -> List[TrackInfo]:
""" """
Process a YouTube URL (video or playlist) Process a YouTube URL (video or playlist)