/* DESCRIPTION file.go provides an implementation of the AVDevice interface for media files. AUTHORS Saxon A. Nelson-Milton LICENSE Copyright (C) 2019 the Australian Ocean Lab (AusOcean) It is free software: you can redistribute it and/or modify them under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License in gpl.txt. If not, see http://www.gnu.org/licenses. */ // Package file provides an implementation of AVDevice for files. package file import ( "errors" "fmt" "io" "os" "sync" "bitbucket.org/ausocean/av/revid/config" "bitbucket.org/ausocean/utils/logging" ) // AVFile is an implementation of the AVDevice interface for a file containg // audio or video data. type AVFile struct { f *os.File path string loop bool isRunning bool log logging.Logger set bool mu sync.Mutex } // NewAVFile returns a new AVFile. func New(l logging.Logger) *AVFile { return &AVFile{log: l} } // NewWith returns a new AVFile with required params provided i.e. the Set // method does not need to be called. func NewWith(l logging.Logger, path string, loop bool) *AVFile { return &AVFile{log: l, path: path, loop: loop, set: true} } // Name returns the name of the device. func (m *AVFile) Name() string { return "File" } // Set simply sets the AVFile's config to the passed config. func (m *AVFile) Set(c config.Config) error { m.path = c.InputPath m.loop = c.Loop m.set = true return nil } // Start will open the file at the location of the InputPath field of the // config struct. func (m *AVFile) Start() error { m.mu.Lock() defer m.mu.Unlock() var err error if !m.set { return errors.New("AVFile has not been set with config") } m.f, err = os.Open(m.path) if err != nil { return fmt.Errorf("could not open media file: %w", err) } m.isRunning = true return nil } // Stop will close the file such that any further reads will fail. func (m *AVFile) Stop() error { m.mu.Lock() defer m.mu.Unlock() err := m.f.Close() if err == nil { m.isRunning = false return nil } return err } // Read implements io.Reader. If start has not been called, or Start has been // called and Stop has since been called, an error is returned. func (m *AVFile) Read(p []byte) (int, error) { m.mu.Lock() defer m.mu.Unlock() if m.f == nil { return 0, errors.New("AV file is closed, AVFile not started") } n, err := m.f.Read(p) if err != nil && err != io.EOF { return n, err } if (n < len(p) || err == io.EOF) && m.loop { m.log.Info("looping input file") // In the case that we reach end of file but loop is true, we want to // seek to start and keep reading from there. _, err = m.f.Seek(0, io.SeekStart) if err != nil { return 0, fmt.Errorf("could not seek to start of file for input loop: %w", err) } // Now that we've seeked to start, let's try reading again. n, err = m.f.Read(p) if err != nil { return n, fmt.Errorf("could not read after start seek: %w", err) } } return n, err } // IsRunning is used to determine if the AVFile device is running. func (m *AVFile) IsRunning() bool { m.mu.Lock() defer m.mu.Unlock() return m.f != nil && m.isRunning }