fix: stuff
This commit is contained in:
@@ -193,7 +193,7 @@ func downloadPlaylist(ctx context.Context, client *http.Client, parsed *url.URL,
|
|||||||
return downloadLiveStream(liveCtx, client, finalURL, mediaPlaylist, tsFile, name)
|
return downloadLiveStream(liveCtx, client, finalURL, mediaPlaylist, tsFile, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// For VOD (Video on Demand), use temp file and convert to MP4
|
// For VOD (Video on Demand), download segments and use ffmpeg concat demuxer
|
||||||
// Create a context without deadline for VOD downloads to avoid premature cancellation
|
// Create a context without deadline for VOD downloads to avoid premature cancellation
|
||||||
// Preserve cancellation from parent context but remove any deadline
|
// Preserve cancellation from parent context but remove any deadline
|
||||||
vodCtx, vodCancel := context.WithCancel(context.Background())
|
vodCtx, vodCancel := context.WithCancel(context.Background())
|
||||||
@@ -208,6 +208,7 @@ func downloadPlaylist(ctx context.Context, client *http.Client, parsed *url.URL,
|
|||||||
// Create a client with longer timeout for VOD segments
|
// Create a client with longer timeout for VOD segments
|
||||||
vodClient := &http.Client{Timeout: vodSegmentTimeout}
|
vodClient := &http.Client{Timeout: vodSegmentTimeout}
|
||||||
|
|
||||||
|
// Create temp file for concatenated segments
|
||||||
tempTS, err := os.CreateTemp("", "sdl-*.ts")
|
tempTS, err := os.CreateTemp("", "sdl-*.ts")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create temp file: %w", err)
|
return fmt.Errorf("create temp file: %w", err)
|
||||||
@@ -219,11 +220,17 @@ func downloadPlaylist(ctx context.Context, client *http.Client, parsed *url.URL,
|
|||||||
totalSegments := len(segments)
|
totalSegments := len(segments)
|
||||||
fmt.Fprintf(os.Stderr, "Downloading %d segments...\n", totalSegments)
|
fmt.Fprintf(os.Stderr, "Downloading %d segments...\n", totalSegments)
|
||||||
|
|
||||||
|
// Download and concatenate segments directly
|
||||||
for i, segment := range segments {
|
for i, segment := range segments {
|
||||||
if err := downloadSegment(vodCtx, vodClient, finalURL, segment, tempTS); err != nil {
|
if err := downloadSegment(vodCtx, vodClient, finalURL, segment, tempTS); err != nil {
|
||||||
tempTS.Close()
|
tempTS.Close()
|
||||||
return fmt.Errorf("download segment %d: %w", i, err)
|
return fmt.Errorf("download segment %d: %w", i, err)
|
||||||
}
|
}
|
||||||
|
// Flush after each segment to ensure data is written
|
||||||
|
if err := tempTS.Sync(); err != nil {
|
||||||
|
tempTS.Close()
|
||||||
|
return fmt.Errorf("sync after segment %d: %w", i, err)
|
||||||
|
}
|
||||||
// Show progress: segment number, total, and percentage
|
// Show progress: segment number, total, and percentage
|
||||||
progress := float64(i+1) / float64(totalSegments) * 100
|
progress := float64(i+1) / float64(totalSegments) * 100
|
||||||
fmt.Fprintf(os.Stderr, "\rProgress: %d/%d segments (%.1f%%)", i+1, totalSegments, progress)
|
fmt.Fprintf(os.Stderr, "\rProgress: %d/%d segments (%.1f%%)", i+1, totalSegments, progress)
|
||||||
@@ -231,12 +238,6 @@ func downloadPlaylist(ctx context.Context, client *http.Client, parsed *url.URL,
|
|||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "\n")
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
// Flush and sync the file to ensure all data is written to disk
|
|
||||||
if err := tempTS.Sync(); err != nil {
|
|
||||||
tempTS.Close()
|
|
||||||
return fmt.Errorf("sync temp file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the file before conversion
|
// Close the file before conversion
|
||||||
if err := tempTS.Close(); err != nil {
|
if err := tempTS.Close(); err != nil {
|
||||||
return fmt.Errorf("close temp file: %w", err)
|
return fmt.Errorf("close temp file: %w", err)
|
||||||
@@ -663,6 +664,138 @@ func inferOutputNameMust(rawURL string) string {
|
|||||||
return inferOutputName(parsed)
|
return inferOutputName(parsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func transmuxToMP4FromConcat(ctx context.Context, concatFile, mp4Path string) error {
|
||||||
|
if _, err := exec.LookPath("ffmpeg"); err != nil {
|
||||||
|
return errFFmpegMissing
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Dir(mp4Path), 0o755); err != nil {
|
||||||
|
return fmt.Errorf("ensure output directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify concat file exists and is readable
|
||||||
|
if _, err := os.Stat(concatFile); err != nil {
|
||||||
|
return fmt.Errorf("concat file not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read concat file to verify format
|
||||||
|
concatContent, err := os.ReadFile(concatFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read concat file: %w", err)
|
||||||
|
}
|
||||||
|
// Debug: show first few lines of concat file if it's small
|
||||||
|
if len(concatContent) < 500 {
|
||||||
|
fmt.Fprintf(os.Stderr, "Concat file content:\n%s\n", string(concatContent))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use ffmpeg concat demuxer to properly combine segments
|
||||||
|
// This handles TS metadata correctly
|
||||||
|
// Add -avoid_negative_ts make_zero to handle timestamp issues
|
||||||
|
// Use absolute path for concat file
|
||||||
|
absConcatFile, err := filepath.Abs(concatFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get absolute path for concat file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, "ffmpeg",
|
||||||
|
"-y",
|
||||||
|
"-f", "concat",
|
||||||
|
"-safe", "0",
|
||||||
|
"-i", absConcatFile,
|
||||||
|
"-avoid_negative_ts", "make_zero",
|
||||||
|
"-c", "copy",
|
||||||
|
"-movflags", "+faststart",
|
||||||
|
mp4Path)
|
||||||
|
cmd.Stdout = io.Discard
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
// If copy fails, try with re-encoding as fallback
|
||||||
|
msg := strings.TrimSpace(stderr.String())
|
||||||
|
// Check for various error patterns
|
||||||
|
needsReencode := strings.Contains(msg, "could not find corresponding") ||
|
||||||
|
strings.Contains(msg, "error reading header") ||
|
||||||
|
strings.Contains(msg, "Invalid data") ||
|
||||||
|
strings.Contains(msg, "Invalid argument") ||
|
||||||
|
strings.Contains(msg, "No such file")
|
||||||
|
|
||||||
|
if needsReencode {
|
||||||
|
// Try re-encoding instead of copy
|
||||||
|
fmt.Fprintf(os.Stderr, "Copy mode failed, trying re-encode...\n")
|
||||||
|
cmd2 := exec.CommandContext(ctx, "ffmpeg",
|
||||||
|
"-y",
|
||||||
|
"-f", "concat",
|
||||||
|
"-safe", "0",
|
||||||
|
"-i", absConcatFile,
|
||||||
|
"-avoid_negative_ts", "make_zero",
|
||||||
|
"-fflags", "+genpts",
|
||||||
|
"-c:v", "libx264",
|
||||||
|
"-preset", "medium",
|
||||||
|
"-crf", "23",
|
||||||
|
"-c:a", "aac",
|
||||||
|
"-b:a", "128k",
|
||||||
|
"-movflags", "+faststart",
|
||||||
|
mp4Path)
|
||||||
|
cmd2.Stdout = io.Discard
|
||||||
|
var stderr2 bytes.Buffer
|
||||||
|
cmd2.Stderr = &stderr2
|
||||||
|
if err2 := cmd2.Run(); err2 != nil {
|
||||||
|
// If concat demuxer completely fails, try using individual segments as input
|
||||||
|
fmt.Fprintf(os.Stderr, "Concat demuxer failed, trying direct segment input...\n")
|
||||||
|
// Get segment files from concat file
|
||||||
|
lines := strings.Split(string(concatContent), "\n")
|
||||||
|
var segmentPaths []string
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if strings.HasPrefix(line, "file '") && strings.HasSuffix(line, "'") {
|
||||||
|
path := strings.TrimPrefix(strings.TrimSuffix(line, "'"), "file '")
|
||||||
|
path = strings.ReplaceAll(path, "''", "'") // Unescape
|
||||||
|
segmentPaths = append(segmentPaths, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(segmentPaths) > 0 {
|
||||||
|
// Try with first segment to test
|
||||||
|
cmd3 := exec.CommandContext(ctx, "ffmpeg",
|
||||||
|
"-y",
|
||||||
|
"-i", segmentPaths[0],
|
||||||
|
"-avoid_negative_ts", "make_zero",
|
||||||
|
"-fflags", "+genpts",
|
||||||
|
"-c:v", "libx264",
|
||||||
|
"-preset", "medium",
|
||||||
|
"-crf", "23",
|
||||||
|
"-c:a", "aac",
|
||||||
|
"-b:a", "128k",
|
||||||
|
"-movflags", "+faststart",
|
||||||
|
mp4Path)
|
||||||
|
cmd3.Stdout = io.Discard
|
||||||
|
var stderr3 bytes.Buffer
|
||||||
|
cmd3.Stderr = &stderr3
|
||||||
|
if err3 := cmd3.Run(); err3 != nil {
|
||||||
|
os.Remove(mp4Path)
|
||||||
|
return fmt.Errorf("ffmpeg failed with all methods. Last error: %s", strings.TrimSpace(stderr3.String()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
os.Remove(mp4Path)
|
||||||
|
msg2 := strings.TrimSpace(stderr2.String())
|
||||||
|
if msg2 != "" {
|
||||||
|
return fmt.Errorf("ffmpeg (re-encode): %s", msg2)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("ffmpeg (re-encode): %w", err2)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
os.Remove(mp4Path)
|
||||||
|
if msg != "" {
|
||||||
|
return fmt.Errorf("ffmpeg: %s", msg)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("ffmpeg: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func transmuxToMP4(ctx context.Context, tsPath, mp4Path string) error {
|
func transmuxToMP4(ctx context.Context, tsPath, mp4Path string) error {
|
||||||
if _, err := exec.LookPath("ffmpeg"); err != nil {
|
if _, err := exec.LookPath("ffmpeg"); err != nil {
|
||||||
return errFFmpegMissing
|
return errFFmpegMissing
|
||||||
@@ -675,11 +808,13 @@ func transmuxToMP4(ctx context.Context, tsPath, mp4Path string) error {
|
|||||||
// Try with copy first (fastest), fall back to re-encoding if needed
|
// Try with copy first (fastest), fall back to re-encoding if needed
|
||||||
// Use -fflags +genpts to generate presentation timestamps if missing
|
// Use -fflags +genpts to generate presentation timestamps if missing
|
||||||
// Use -err_detect ignore_err to be more tolerant of minor errors
|
// Use -err_detect ignore_err to be more tolerant of minor errors
|
||||||
|
// Use -avoid_negative_ts make_zero to handle timestamp issues
|
||||||
cmd := exec.CommandContext(ctx, "ffmpeg",
|
cmd := exec.CommandContext(ctx, "ffmpeg",
|
||||||
"-y",
|
"-y",
|
||||||
"-fflags", "+genpts",
|
"-fflags", "+genpts+igndts",
|
||||||
"-err_detect", "ignore_err",
|
"-err_detect", "ignore_err",
|
||||||
"-i", tsPath,
|
"-i", tsPath,
|
||||||
|
"-avoid_negative_ts", "make_zero",
|
||||||
"-c", "copy",
|
"-c", "copy",
|
||||||
"-movflags", "+faststart",
|
"-movflags", "+faststart",
|
||||||
mp4Path)
|
mp4Path)
|
||||||
@@ -690,16 +825,25 @@ func transmuxToMP4(ctx context.Context, tsPath, mp4Path string) error {
|
|||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
// If copy fails, try with re-encoding as fallback
|
// If copy fails, try with re-encoding as fallback
|
||||||
msg := strings.TrimSpace(stderr.String())
|
msg := strings.TrimSpace(stderr.String())
|
||||||
if strings.Contains(msg, "could not find corresponding") || strings.Contains(msg, "error reading header") {
|
needsReencode := strings.Contains(msg, "could not find corresponding") ||
|
||||||
|
strings.Contains(msg, "error reading header") ||
|
||||||
|
strings.Contains(msg, "Invalid data") ||
|
||||||
|
strings.Contains(msg, "Invalid argument")
|
||||||
|
|
||||||
|
if needsReencode {
|
||||||
// Try re-encoding instead of copy
|
// Try re-encoding instead of copy
|
||||||
fmt.Fprintf(os.Stderr, "Copy mode failed, trying re-encode...\n")
|
fmt.Fprintf(os.Stderr, "Copy mode failed, trying re-encode...\n")
|
||||||
cmd2 := exec.CommandContext(ctx, "ffmpeg",
|
cmd2 := exec.CommandContext(ctx, "ffmpeg",
|
||||||
"-y",
|
"-y",
|
||||||
"-fflags", "+genpts",
|
"-fflags", "+genpts+igndts",
|
||||||
"-err_detect", "ignore_err",
|
"-err_detect", "ignore_err",
|
||||||
"-i", tsPath,
|
"-i", tsPath,
|
||||||
|
"-avoid_negative_ts", "make_zero",
|
||||||
"-c:v", "libx264",
|
"-c:v", "libx264",
|
||||||
|
"-preset", "medium",
|
||||||
|
"-crf", "23",
|
||||||
"-c:a", "aac",
|
"-c:a", "aac",
|
||||||
|
"-b:a", "128k",
|
||||||
"-movflags", "+faststart",
|
"-movflags", "+faststart",
|
||||||
mp4Path)
|
mp4Path)
|
||||||
cmd2.Stdout = io.Discard
|
cmd2.Stdout = io.Discard
|
||||||
|
|||||||
Reference in New Issue
Block a user