fix: stuff
This commit is contained in:
@@ -208,29 +208,42 @@ 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
|
// Create temp directory for segments
|
||||||
tempTS, err := os.CreateTemp("", "sdl-*.ts")
|
tempDir, err := os.MkdirTemp("", "sdl-segments-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create temp file: %w", err)
|
return fmt.Errorf("create temp directory: %w", err)
|
||||||
}
|
}
|
||||||
tempPath := tempTS.Name()
|
defer os.RemoveAll(tempDir)
|
||||||
defer os.Remove(tempPath)
|
|
||||||
|
|
||||||
segments := collectSegments(mediaPlaylist)
|
segments := collectSegments(mediaPlaylist)
|
||||||
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
|
// Download each segment to a separate file
|
||||||
|
segmentFiles := make([]string, 0, totalSegments)
|
||||||
for i, segment := range segments {
|
for i, segment := range segments {
|
||||||
if err := downloadSegment(vodCtx, vodClient, finalURL, segment, tempTS); err != nil {
|
segmentFile := filepath.Join(tempDir, fmt.Sprintf("segment-%05d.ts", i))
|
||||||
tempTS.Close()
|
file, err := os.Create(segmentFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create segment file %d: %w", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := downloadSegment(vodCtx, vodClient, finalURL, segment, file); err != nil {
|
||||||
|
file.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 {
|
if err := file.Sync(); err != nil {
|
||||||
tempTS.Close()
|
file.Close()
|
||||||
return fmt.Errorf("sync after segment %d: %w", i, err)
|
return fmt.Errorf("sync segment file %d: %w", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := file.Close(); err != nil {
|
||||||
|
return fmt.Errorf("close segment file %d: %w", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
segmentFiles = append(segmentFiles, segmentFile)
|
||||||
|
|
||||||
// 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)
|
||||||
@@ -238,23 +251,31 @@ func downloadPlaylist(ctx context.Context, client *http.Client, parsed *url.URL,
|
|||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "\n")
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
// Close the file before conversion
|
// Create concat file for ffmpeg concat protocol (not demuxer)
|
||||||
if err := tempTS.Close(); err != nil {
|
// The concat protocol format is different: just file paths, one per line
|
||||||
return fmt.Errorf("close temp file: %w", err)
|
concatFile := filepath.Join(tempDir, "concat.txt")
|
||||||
|
concatF, err := os.Create(concatFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create concat file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the file exists and has content
|
for _, segFile := range segmentFiles {
|
||||||
stat, err := os.Stat(tempPath)
|
// Use absolute path for concat protocol
|
||||||
if err != nil {
|
absPath, err := filepath.Abs(segFile)
|
||||||
return fmt.Errorf("stat temp file: %w", err)
|
if err != nil {
|
||||||
|
concatF.Close()
|
||||||
|
return fmt.Errorf("get absolute path: %w", err)
|
||||||
|
}
|
||||||
|
// Concat protocol format: just the path, one per line
|
||||||
|
fmt.Fprintf(concatF, "%s\n", absPath)
|
||||||
}
|
}
|
||||||
if stat.Size() == 0 {
|
if err := concatF.Close(); err != nil {
|
||||||
return fmt.Errorf("downloaded file is empty")
|
return fmt.Errorf("close concat file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mp4Name := ensureMP4Extension(name)
|
mp4Name := ensureMP4Extension(name)
|
||||||
fmt.Fprintf(os.Stderr, "Converting to MP4 (%.2f MB)...\n", float64(stat.Size())/1024/1024)
|
fmt.Fprintf(os.Stderr, "Converting to MP4...\n")
|
||||||
if err := transmuxToMP4(vodCtx, tempPath, mp4Name); err != nil {
|
if err := transmuxToMP4FromConcatProtocol(vodCtx, concatFile, mp4Name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "Complete: %s\n", mp4Name)
|
fmt.Fprintf(os.Stderr, "Complete: %s\n", mp4Name)
|
||||||
@@ -664,6 +685,99 @@ func inferOutputNameMust(rawURL string) string {
|
|||||||
return inferOutputName(parsed)
|
return inferOutputName(parsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func transmuxToMP4FromConcatProtocol(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use ffmpeg concat protocol (not demuxer) - this is designed for TS files
|
||||||
|
// Format: concat:file1.ts|file2.ts|file3.ts
|
||||||
|
// Or we can use the file list with concat protocol
|
||||||
|
absConcatFile, err := filepath.Abs(concatFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get absolute path for concat file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the concat file to build the concat protocol string
|
||||||
|
concatContent, err := os.ReadFile(absConcatFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read concat file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(string(concatContent), "\n")
|
||||||
|
var segmentPaths []string
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line != "" {
|
||||||
|
segmentPaths = append(segmentPaths, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(segmentPaths) == 0 {
|
||||||
|
return fmt.Errorf("no segments found in concat file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build concat protocol string: concat:file1.ts|file2.ts|...
|
||||||
|
concatInput := "concat:" + strings.Join(segmentPaths, "|")
|
||||||
|
|
||||||
|
// Use concat protocol with better flags for TS files
|
||||||
|
cmd := exec.CommandContext(ctx, "ffmpeg",
|
||||||
|
"-y",
|
||||||
|
"-i", concatInput,
|
||||||
|
"-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
|
||||||
|
msg := strings.TrimSpace(stderr.String())
|
||||||
|
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 {
|
||||||
|
fmt.Fprintf(os.Stderr, "Copy mode failed, trying re-encode...\n")
|
||||||
|
cmd2 := exec.CommandContext(ctx, "ffmpeg",
|
||||||
|
"-y",
|
||||||
|
"-i", concatInput,
|
||||||
|
"-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 {
|
||||||
|
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 transmuxToMP4FromConcat(ctx context.Context, concatFile, mp4Path string) error {
|
func transmuxToMP4FromConcat(ctx context.Context, concatFile, mp4Path string) error {
|
||||||
if _, err := exec.LookPath("ffmpeg"); err != nil {
|
if _, err := exec.LookPath("ffmpeg"); err != nil {
|
||||||
return errFFmpegMissing
|
return errFFmpegMissing
|
||||||
|
|||||||
Reference in New Issue
Block a user