@@ -28,7 +28,6 @@ import (
28
28
"time"
29
29
"unicode"
30
30
31
- "github.com/asticode/go-astiav"
32
31
"github.com/davidbyttow/govips/v2/vips"
33
32
"github.com/gabriel-vasile/mimetype"
34
33
"github.com/kenshaw/colors"
@@ -78,6 +77,7 @@ type Args struct {
78
77
FontBg * colors.Color `ox:"font background color,default:white"`
79
78
FontDPI int `ox:"font dpi,default:100,name:font-dpi"`
80
79
FontMargin int `ox:"margin,default:5"`
80
+ TimeCode time.Duration `ox:"time code,short:t"`
81
81
VipsConcurrency int `ox:"vips concurrency,default:$NUMCPU"`
82
82
83
83
ctx context.Context
@@ -200,7 +200,7 @@ func (args *Args) renderFile(name string) (image.Image, string, error) {
200
200
case strings .HasPrefix (typ , "font/" ):
201
201
g = args .renderFont
202
202
case strings .HasPrefix (typ , "video/" ):
203
- g , notStream = args .renderAstiav , true
203
+ g , notStream = args .renderFfmpeg , true
204
204
default :
205
205
return nil , "" , fmt .Errorf ("mime type %q not supported" , typ )
206
206
}
@@ -295,40 +295,58 @@ func (args *Args) renderVips(r io.Reader, name string) (image.Image, error) {
295
295
return args .vipsExport (v )
296
296
}
297
297
298
- // renderAstiv renders the image using the astiav (ffmpeg) library.
299
- func (args * Args ) renderAstiav (_ io.Reader , name string ) (image.Image , error ) {
300
- astiavOnce .Do (astiavInit (args .logger , args .Verbose ))
301
- in := astiav .AllocFormatContext ()
302
- defer in .Free ()
303
- if err := in .OpenInput (name , nil , nil ); err != nil {
304
- return nil , err
305
- }
306
- defer in .CloseInput ()
307
- if err := in .FindStreamInfo (nil ); err != nil {
298
+ // renderFfmpeg renders the image using the ffmpeg command.
299
+ func (args * Args ) renderFfmpeg (_ io.Reader , pathName string ) (image.Image , error ) {
300
+ var err error
301
+ ffmpegOnce .Do (func () {
302
+ ffmpegPath , err = exec .LookPath ("ffmpeg" )
303
+ })
304
+ switch {
305
+ case err != nil :
308
306
return nil , err
307
+ case ffmpegPath == "" :
308
+ return nil , errors .New ("ffmpeg not in path" )
309
309
}
310
- for i , is := range in .Streams () {
311
- p := is .CodecParameters ()
312
- typ := p .MediaType ()
313
- if typ != astiav .MediaTypeVideo {
314
- continue
310
+ // ffmpeg -loglevel panic -hide_banner -ss $t -i *.mkv -vframes 1 -q:v 1
311
+ params := []string {
312
+ `-hide_banner` ,
313
+ // `-ss`, ``,
314
+ `-i` , pathName ,
315
+ `-vframes` , `1` ,
316
+ `-q:v` , `1` ,
317
+ `-f` , `apng` ,
318
+ `-` ,
319
+ }
320
+ args .logger ("executing: %s %s" , ffmpegPath , strings .Join (params , " " ))
321
+ start := time .Now ()
322
+ cmd := exec .CommandContext (
323
+ args .ctx ,
324
+ ffmpegPath ,
325
+ params ... ,
326
+ )
327
+ var buf , stderr bytes.Buffer
328
+ cmd .Stdout , cmd .Stderr = & buf , & stderr
329
+ if err := cmd .Run (); err != nil {
330
+ errstr := stderr .String ()
331
+ if len (errstr ) > 100 {
332
+ errstr = errstr [:100 ]
315
333
}
316
- args .logger ("stream %d: %s" , i , typ )
317
- rate := ox .NewRate (p .BitRate (), time .Second )
318
- args .logger (
319
- " bit rate: %s, pixel format: %v, time base: %v, duration: %v, frames: %v" ,
320
- rate , p .PixelFormat (), is .TimeBase (), is .Duration (), is .NbFrames (),
321
- )
334
+ return nil , fmt .Errorf ("%w: %s" , err , errstr )
322
335
}
323
- return nil , errors .New ("oops!" )
336
+ args .logger ("ffmpeg render: %v" , time .Now ().Sub (start ))
337
+ return png .Decode (& buf )
324
338
}
325
339
326
340
// renderLibreOffice renders the image using the `soffice` command.
327
341
func (args * Args ) renderLibreOffice (_ io.Reader , pathName string ) (image.Image , error ) {
342
+ var err error
328
343
sofficeOnce .Do (func () {
329
- sofficePath , _ = exec .LookPath ("soffice" )
344
+ sofficePath , err = exec .LookPath ("soffice" )
330
345
})
331
- if sofficePath == "" {
346
+ switch {
347
+ case err != nil :
348
+ return nil , err
349
+ case sofficePath == "" :
332
350
return nil , errors .New ("soffice not in path" )
333
351
}
334
352
tmpDir , err := os .MkdirTemp ("" , name + "." )
@@ -526,26 +544,6 @@ func vipsLevel(level vips.LogLevel) string {
526
544
return fmt .Sprintf ("(%d)" , level )
527
545
}
528
546
529
- // astiavInit initializes the astiav package.
530
- func astiavInit (logger func (string , ... any ), verbose bool ) func () {
531
- return func () {
532
- level := astiav .LogLevelQuiet
533
- if verbose {
534
- level = astiav .LogLevelDebug
535
- }
536
- astiav .SetLogLevel (level )
537
- astiav .SetLogCallback (func (c astiav.Classer , l astiav.LogLevel , fmt , msg string ) {
538
- var cs string
539
- if c != nil {
540
- if cl := c .Class (); cl != nil {
541
- cs = "class: " + cl .String ()
542
- }
543
- }
544
- logger ("astiav %d: %s%s" , l , strings .TrimSpace (msg ), cs )
545
- })
546
- }
547
- }
548
-
549
547
type files struct {
550
548
args * Args
551
549
}
@@ -698,6 +696,19 @@ func isLibreOffice(typ, ext string) bool {
698
696
return false
699
697
}
700
698
699
+ var urlRE = regexp .MustCompile (`(?i)^https?://` )
700
+
701
+ var (
702
+ vipsOnce sync.Once
703
+ sofficeOnce sync.Once
704
+ ffmpegOnce sync.Once
705
+ )
706
+
707
+ var (
708
+ sofficePath string
709
+ ffmpegPath string
710
+ )
711
+
701
712
// extensions are the extensions to check for directories.
702
713
var extensions = map [string ]bool {
703
714
"3g2" : true ,
@@ -764,13 +775,3 @@ var extensions = map[string]bool{
764
775
"xlsx" : true ,
765
776
"xpm" : true ,
766
777
}
767
-
768
- var urlRE = regexp .MustCompile (`(?i)^https?://` )
769
-
770
- var (
771
- vipsOnce sync.Once
772
- astiavOnce sync.Once
773
- sofficeOnce sync.Once
774
- )
775
-
776
- var sofficePath string
0 commit comments