Add cross header.
[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 #include "lib/cross.h"
28
29 using namespace std;
30 using namespace boost;
31
32 void
33 help (string n)
34 {
35         cerr << "Syntax: " << n << " [--help] [--chop-audio-start] [--chop-audio-end] --film <film>\n";
36 }
37
38 void
39 sox (vector<string> const & audio_files, string const & process)
40 {
41         for (vector<string>::const_iterator i = audio_files.begin(); i != audio_files.end(); ++i) {
42                 stringstream cmd;
43                 cmd << "sox \"" << *i << "\" -t wav \"" << *i << ".tmp\" " << process;
44                 cout << "> " << cmd.str() << "\n";
45                 int r = ::system (cmd.str().c_str());
46                 if (r == -1 || WEXITSTATUS (r) != 0) {
47                         cerr << "fixlengths: call to sox failed.\n";
48                         exit (EXIT_FAILURE);
49                 }
50                 filesystem::rename (*i + ".tmp", *i);
51         }
52 }
53
54 int main (int argc, char* argv[])
55 {
56         string film_dir;
57         bool chop_audio_start = false;
58         bool chop_audio_end = false;
59         bool pad_audio_end = false;
60         
61         while (1) {
62                 static struct option long_options[] = {
63                         { "help", no_argument, 0, 'h' },
64                         { "chop-audio-start", no_argument, 0, 'c' },
65                         { "chop-audio-end", no_argument, 0, 'd' },
66                         { "pad-audio-end", no_argument, 0, 'p' },
67                         { "film", required_argument, 0, 'f' },
68                         { 0, 0, 0, 0 }
69                 };
70
71                 int option_index = 0;
72                 int c = getopt_long (argc, argv, "hcf:", long_options, &option_index);
73
74                 if (c == -1) {
75                         break;
76                 }
77
78                 switch (c) {
79                 case 'h':
80                         help (argv[0]);
81                         exit (EXIT_SUCCESS);
82                 case 'c':
83                         chop_audio_start = true;
84                         break;
85                 case 'd':
86                         chop_audio_end = true;
87                         break;
88                 case 'p':
89                         pad_audio_end = true;
90                         break;
91                 case 'f':
92                         film_dir = optarg;
93                         break;
94                 }
95         }
96
97         if (film_dir.empty ()) {
98                 help (argv[0]);
99                 exit (EXIT_FAILURE);
100         }
101
102         dvdomatic_setup ();
103
104         Film* film = 0;
105         try {
106                 film = new Film (film_dir, true);
107         } catch (std::exception& e) {
108                 cerr << argv[0] << ": error reading film `" << film_dir << "' (" << e.what() << ")\n";
109                 exit (EXIT_FAILURE);
110         }
111
112         /* XXX: hack */
113         int video_frames = 0;
114         for (filesystem::directory_iterator i = filesystem::directory_iterator (film->j2k_dir()); i != filesystem::directory_iterator(); ++i) {
115                 ++video_frames;
116         }
117
118         float const video_length = video_frames / film->frames_per_second();
119         cout << "Video length: " << video_length << " (" << video_frames << " frames at " << film->frames_per_second() << " frames per second).\n";
120
121         vector<string> audio_files = film->audio_files ();
122         if (audio_files.empty ()) {
123                 cerr << argv[0] << ": film has no audio files.\n";
124                 exit (EXIT_FAILURE);
125         }
126
127         sf_count_t audio_frames = 0;
128         int audio_sample_rate = 0;
129
130         for (vector<string>::iterator i = audio_files.begin(); i != audio_files.end(); ++i) {
131                 SF_INFO info;
132                 info.format = 0;
133                 SNDFILE* sf = sf_open (i->c_str(), SFM_READ, &info);
134                 if (sf == 0) {
135                         cerr << argv[0] << ": could not open WAV file for reading.\n";
136                         exit (EXIT_FAILURE);
137                 }
138
139                 if (audio_frames == 0) {
140                         audio_frames = info.frames;
141                 }
142
143                 if (audio_sample_rate == 0) {
144                         audio_sample_rate = info.samplerate;
145                 }
146
147                 if (audio_frames != info.frames) {
148                         cerr << argv[0] << ": audio files have differing lengths.\n";
149                         exit (EXIT_FAILURE);
150                 }
151
152                 if (audio_sample_rate != info.samplerate) {
153                         cerr << argv[0] << ": audio files have differing sample rates.\n";
154                         exit (EXIT_FAILURE);
155                 }
156
157                 sf_close (sf);
158         }
159
160         float const audio_length = audio_frames / float (audio_sample_rate);
161
162         cout << "Audio length: " << audio_length << " (" << audio_frames << " frames at " << audio_sample_rate << " frames per second).\n";
163
164         cout << "\n";
165
166         if (audio_length > video_length) {
167                 cout << setprecision (3);
168                 cout << "Audio " << (audio_length - video_length) << "s longer than video.\n";
169                 
170                 float const delta = audio_length - video_length;
171                 int const delta_samples = delta * audio_sample_rate;
172                 
173                 if (chop_audio_start) {
174                         cout << "Chopping difference off the start of the audio.\n";
175
176                         stringstream s;
177                         s << "trim " << delta_samples << "s";
178                         sox (audio_files, s.str ());
179                         
180                 } else if (chop_audio_end) {
181                         cout << "Chopping difference off the end of the audio.\n";
182
183                         stringstream s;
184                         s << "reverse trim " << delta_samples << "s reverse";
185                         sox (audio_files, s.str ());
186
187                 } else {
188                         cout << "Re-run with --chop-audio-start or --chop-audio-end, perhaps.\n";
189                 }
190
191         } else if (audio_length < video_length) {
192                 cout << setprecision (3);
193                 cout << "Audio " << (video_length - audio_length) << "s shorter than video.\n";
194
195                 if (pad_audio_end) {
196
197                         float const delta = video_length - audio_length;
198                         int const delta_samples = delta * audio_sample_rate;
199                         stringstream s;
200                         s << "pad 0 " << delta_samples << "s";
201                         sox (audio_files, s.str ());
202                 
203                 } else {
204                         cout << "Re-run with --pad-audio-end, perhaps.\n";
205                 }
206         }
207         
208
209         return 0;
210 }