Move things round a bit.
[dcpomatic.git] / src / tools / fixlengths.cc
1 /*
2     Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <stdexcept>
21 #include <iostream>
22 #include <iomanip>
23 #include <getopt.h>
24 #include <sndfile.h>
25 #include <boost/filesystem.hpp>
26 #include "lib/film.h"
27
28 using namespace std;
29 using namespace boost;
30
31 void
32 help (string n)
33 {
34         cerr << "Syntax: " << n << " [--help] [--chop-audio-start] [--chop-audio-end] --film <film>\n";
35 }
36
37 void
38 sox (vector<string> const & audio_files, string const & process)
39 {
40         for (vector<string>::const_iterator i = audio_files.begin(); i != audio_files.end(); ++i) {
41                 stringstream cmd;
42                 cmd << "sox \"" << *i << "\" -t wav \"" << *i << ".tmp\" " << process;
43                 cout << "> " << cmd.str() << "\n";
44                 int r = ::system (cmd.str().c_str());
45                 if (r == -1 || WEXITSTATUS (r) != 0) {
46                         cerr << "fixlengths: call to sox failed.\n";
47                         exit (EXIT_FAILURE);
48                 }
49                 filesystem::rename (*i + ".tmp", *i);
50         }
51 }
52
53 int main (int argc, char* argv[])
54 {
55         string film_dir;
56         bool chop_audio_start = false;
57         bool chop_audio_end = false;
58         bool pad_audio_end = false;
59         
60         while (1) {
61                 static struct option long_options[] = {
62                         { "help", no_argument, 0, 'h' },
63                         { "chop-audio-start", no_argument, 0, 'c' },
64                         { "chop-audio-end", no_argument, 0, 'd' },
65                         { "pad-audio-end", no_argument, 0, 'p' },
66                         { "film", required_argument, 0, 'f' },
67                         { 0, 0, 0, 0 }
68                 };
69
70                 int option_index = 0;
71                 int c = getopt_long (argc, argv, "hcf:", long_options, &option_index);
72
73                 if (c == -1) {
74                         break;
75                 }
76
77                 switch (c) {
78                 case 'h':
79                         help (argv[0]);
80                         exit (EXIT_SUCCESS);
81                 case 'c':
82                         chop_audio_start = true;
83                         break;
84                 case 'd':
85                         chop_audio_end = true;
86                         break;
87                 case 'p':
88                         pad_audio_end = true;
89                         break;
90                 case 'f':
91                         film_dir = optarg;
92                         break;
93                 }
94         }
95
96         if (film_dir.empty ()) {
97                 help (argv[0]);
98                 exit (EXIT_FAILURE);
99         }
100
101         dvdomatic_setup ();
102
103         Film* film = 0;
104         try {
105                 film = new Film (film_dir, true);
106         } catch (std::exception& e) {
107                 cerr << argv[0] << ": error reading film `" << film_dir << "' (" << e.what() << ")\n";
108                 exit (EXIT_FAILURE);
109         }
110
111         /* XXX: hack */
112         int video_frames = 0;
113         for (filesystem::directory_iterator i = filesystem::directory_iterator (film->j2k_dir()); i != filesystem::directory_iterator(); ++i) {
114                 ++video_frames;
115         }
116
117         float const video_length = video_frames / film->frames_per_second();
118         cout << "Video length: " << video_length << " (" << video_frames << " frames at " << film->frames_per_second() << " frames per second).\n";
119
120         vector<string> audio_files = film->audio_files ();
121         if (audio_files.empty ()) {
122                 cerr << argv[0] << ": film has no audio files.\n";
123                 exit (EXIT_FAILURE);
124         }
125
126         sf_count_t audio_frames = 0;
127         int audio_sample_rate = 0;
128
129         for (vector<string>::iterator i = audio_files.begin(); i != audio_files.end(); ++i) {
130                 SF_INFO info;
131                 info.format = 0;
132                 SNDFILE* sf = sf_open (i->c_str(), SFM_READ, &info);
133                 if (sf == 0) {
134                         cerr << argv[0] << ": could not open WAV file for reading.\n";
135                         exit (EXIT_FAILURE);
136                 }
137
138                 if (audio_frames == 0) {
139                         audio_frames = info.frames;
140                 }
141
142                 if (audio_sample_rate == 0) {
143                         audio_sample_rate = info.samplerate;
144                 }
145
146                 if (audio_frames != info.frames) {
147                         cerr << argv[0] << ": audio files have differing lengths.\n";
148                         exit (EXIT_FAILURE);
149                 }
150
151                 if (audio_sample_rate != info.samplerate) {
152                         cerr << argv[0] << ": audio files have differing sample rates.\n";
153                         exit (EXIT_FAILURE);
154                 }
155
156                 sf_close (sf);
157         }
158
159         float const audio_length = audio_frames / float (audio_sample_rate);
160
161         cout << "Audio length: " << audio_length << " (" << audio_frames << " frames at " << audio_sample_rate << " frames per second).\n";
162
163         cout << "\n";
164
165         if (audio_length > video_length) {
166                 cout << setprecision (3);
167                 cout << "Audio " << (audio_length - video_length) << "s longer than video.\n";
168                 
169                 float const delta = audio_length - video_length;
170                 int const delta_samples = delta * audio_sample_rate;
171                 
172                 if (chop_audio_start) {
173                         cout << "Chopping difference off the start of the audio.\n";
174
175                         stringstream s;
176                         s << "trim " << delta_samples << "s";
177                         sox (audio_files, s.str ());
178                         
179                 } else if (chop_audio_end) {
180                         cout << "Chopping difference off the end of the audio.\n";
181
182                         stringstream s;
183                         s << "reverse trim " << delta_samples << "s reverse";
184                         sox (audio_files, s.str ());
185
186                 } else {
187                         cout << "Re-run with --chop-audio-start or --chop-audio-end, perhaps.\n";
188                 }
189
190         } else if (audio_length < video_length) {
191                 cout << setprecision (3);
192                 cout << "Audio " << (video_length - audio_length) << "s shorter than video.\n";
193
194                 if (pad_audio_end) {
195
196                         float const delta = video_length - audio_length;
197                         int const delta_samples = delta * audio_sample_rate;
198                         stringstream s;
199                         s << "pad 0 " << delta_samples << "s";
200                         sox (audio_files, s.str ());
201                 
202                 } else {
203                         cout << "Re-run with --pad-audio-end, perhaps.\n";
204                 }
205         }
206         
207
208         return 0;
209 }