Index: trunk/extensions/Score/Score.i18n.php |
— | — | @@ -39,12 +39,11 @@ |
40 | 40 | 'score-invalidlang' => 'Invalid score language lang="$1". Currently recognised languages are lang="lilypond" (the default) and lang="ABC".', |
41 | 41 | 'score-invalidoggoverride' => 'The file you specified with override_ogg is invalid. Please specify the file name only, omit <nowiki>[[…]]</nowiki> and the "File:" prefix.', |
42 | 42 | 'score-noabcinput' => 'ABC source file $1 could not be created.', |
43 | | - 'score-nofactory' => 'Failed to create LilyPond factory directory.', |
44 | 43 | 'score-noimages' => 'No score images were generated. Please check your score code.', |
45 | 44 | 'score-noinput' => 'Failed to create LilyPond input file $1.', |
46 | 45 | 'score-noogghandler' => 'Ogg/Vorbis conversion requires an installed and configured OggHandler extension, see [//www.mediawiki.org/wiki/Extension:OggHandler Extension:OggHandler].', |
47 | 46 | 'score-nomidi' => 'No MIDI file generated despite being requested. If you are working in raw LilyPond mode, make sure to provide a proper \midi block.', |
48 | | - 'score-nooutput' => 'Failed to create LilyPond image directory $1.', |
| 47 | + 'score-nooutput' => 'Failed to create output directory $1.', |
49 | 48 | 'score-notexecutable' => 'Could not execute LilyPond: $1 is not an executable file. Make sure <code>$wgScoreLilyPond</code> is set correctly.', |
50 | 49 | 'score-novorbislink' => 'Unable to generate Ogg/Vorbis link: $1', |
51 | 50 | 'score-oggconversionerr' => 'Unable to convert MIDI to Ogg/Vorbis: |
— | — | @@ -75,12 +74,11 @@ |
76 | 75 | 'score-invalidlang' => 'Displayed if the lang="…" attribute contains an unrecognised score language. $1 is the unrecognised language.', |
77 | 76 | 'score-invalidoggoverride' => 'Displayed if the file specified with the override_ogg="…" attribute is invalid.', |
78 | 77 | 'score-noabcinput' => 'Displayed if an ABC source file could not be created for lang="ABC". $1 is the path to the file that could not be created.', |
79 | | - 'score-nofactory' => 'Displayed if the LilyPond/ImageMagick working directory cannot be created.', |
80 | 78 | 'score-noimages' => 'Displayed if no score images were rendered.', |
81 | 79 | 'score-noinput' => 'Displayed if the LilyPond input file cannot be created. $1 is the path to the input file.', |
82 | 80 | 'score-noogghandler' => 'Displayed if Ogg/Vorbis rendering was requested without the OggHandler extension installed.', |
83 | 81 | 'score-nomidi' => 'Displayed if MIDI file generation was requested but no MIDI file was generated.', |
84 | | - 'score-nooutput' => 'Displayed if the LilyPond image/midi dir cannot be created. $1 is the name of the directory.', |
| 82 | + 'score-nooutput' => 'Displayed if an output directory could not be created. $1 is the name of the directory.', |
85 | 83 | 'score-notexecutable' => 'Displayed if LilyPond binary cannot be executed. $1 is the path to the LilyPond binary.', |
86 | 84 | 'score-novorbislink' => 'Displayed if an Ogg/Vorbis link could not be generated. $1 is the explanation why.', |
87 | 85 | 'score-oggconversionerr' => 'Displayed if the MIDI to Ogg/Vorbis conversion failed. $1 is the error (generally big block of text in a pre tag)', |
Index: trunk/extensions/Score/Score.body.php |
— | — | @@ -90,7 +90,7 @@ |
91 | 91 | } |
92 | 92 | |
93 | 93 | /** |
94 | | - * Score class |
| 94 | + * Score class. |
95 | 95 | */ |
96 | 96 | class Score { |
97 | 97 | /** |
— | — | @@ -165,15 +165,17 @@ |
166 | 166 | * Creates the specified directory if it does not exist yet. |
167 | 167 | * Otherwise does nothing. |
168 | 168 | * |
169 | | - * @param $path string path to directory to be created. |
| 169 | + * @param $path string Path to directory to be created. |
| 170 | + * @param $mode integer Chmod value of the new directory. |
170 | 171 | * |
171 | | - * @throws ScoreException if the directory does not exist and could not be created. |
| 172 | + * @throws ScoreException if the directory does not exist and could not |
| 173 | + * be created. |
172 | 174 | */ |
173 | | - private static function createFactory( $path ) { |
| 175 | + private static function createDirectory( $path, $mode = null ) { |
174 | 176 | if ( !is_dir( $path ) ) { |
175 | | - $rc = wfMkdirParents( $path, 0700, __METHOD__ ); |
| 177 | + $rc = wfMkdirParents( $path, $mode, __METHOD__ ); |
176 | 178 | if ( !$rc ) { |
177 | | - throw new ScoreException( wfMessage( 'score-nofactory' ) ); |
| 179 | + throw new ScoreException( wfMessage( 'score-nooutput', $path ) ); |
178 | 180 | } |
179 | 181 | } |
180 | 182 | } |
— | — | @@ -189,11 +191,17 @@ |
190 | 192 | * @return Image link HTML, and possibly anchor to MIDI file. |
191 | 193 | */ |
192 | 194 | public static function render( $code, array $args, Parser $parser, PPFrame $frame ) { |
| 195 | + global $wgTmpDirectory, $wgUploadDirectory, $wgUploadPath; |
| 196 | + |
193 | 197 | $prof = new ScopedProfiling( __METHOD__ ); |
194 | 198 | |
195 | 199 | try { |
196 | | - $options = array(); |
| 200 | + $options = array(); // options to self::generateHTML() |
197 | 201 | |
| 202 | + /* temporary working directory to use */ |
| 203 | + $fuzz = md5( mt_rand() ); |
| 204 | + $options['factory_directory'] = $wgTmpDirectory . "/MWLP.$fuzz"; |
| 205 | + |
198 | 206 | /* Score language selection */ |
199 | 207 | if ( array_key_exists( 'lang', $args ) ) { |
200 | 208 | $options['lang'] = $args['lang']; |
— | — | @@ -204,6 +212,24 @@ |
205 | 213 | throw new ScoreException( wfMessage( 'score-invalidlang', $options['lang'] ) ); |
206 | 214 | } |
207 | 215 | |
| 216 | + /* image file path and URL prefixes */ |
| 217 | + $imageCacheName = wfBaseConvert( sha1( $code ), 16, 36, 31 ); |
| 218 | + $imagePrefixEnd = self::LILYPOND_DIR_NAME . '/' |
| 219 | + . "{$imageCacheName[0]}/{$imageCacheName[0]}{$imageCacheName[1]}/$imageCacheName"; |
| 220 | + $options['image_path_prefix'] = "$wgUploadDirectory/$imagePrefixEnd"; |
| 221 | + $options['image_url_prefix'] = "$wgUploadPath/$imagePrefixEnd"; |
| 222 | + |
| 223 | + /* Midi linking? */ |
| 224 | + if ( array_key_exists( 'midi', $args ) ) { |
| 225 | + $options['link_midi'] = $args['midi']; |
| 226 | + } else { |
| 227 | + $options['link_midi'] = false; |
| 228 | + } |
| 229 | + if ( $options['link_midi'] ) { |
| 230 | + $options['midi_path'] = "{$options['image_path_prefix']}.midi"; |
| 231 | + $options['midi_url'] = "{$options['image_url_prefix']}.midi"; |
| 232 | + } |
| 233 | + |
208 | 234 | /* Override OGG file? */ |
209 | 235 | if ( array_key_exists( 'override_ogg', $args ) ) { |
210 | 236 | $t = Title::newFromText( $args['override_ogg'], NS_FILE ); |
— | — | @@ -213,29 +239,27 @@ |
214 | 240 | if ( !$t->isKnown() ) { |
215 | 241 | throw new ScoreException( wfMessage( 'score-oggoverridenotfound' ) ); |
216 | 242 | } |
217 | | - $options['override_ogg'] = $args['override_ogg']; |
| 243 | + $options['override_ogg'] = true; |
| 244 | + $options['ogg_name'] = $args['override_ogg']; |
218 | 245 | } else { |
219 | 246 | $options['override_ogg'] = false; |
220 | 247 | } |
221 | 248 | |
222 | 249 | /* Vorbis rendering? */ |
223 | 250 | if ( array_key_exists( 'vorbis', $args ) ) { |
224 | | - $options['vorbis'] = $args['vorbis']; |
| 251 | + $options['generate_vorbis'] = $args['vorbis']; |
225 | 252 | } else { |
226 | | - $options['vorbis'] = false; |
| 253 | + $options['generate_vorbis'] = false; |
227 | 254 | } |
228 | | - if ( $options['vorbis'] && !( class_exists( 'OggHandler' ) && class_exists( 'OggAudioDisplay' ) ) ) { |
| 255 | + if ( $options['generate_vorbis'] && !( class_exists( 'OggHandler' ) && class_exists( 'OggAudioDisplay' ) ) ) { |
229 | 256 | throw new ScoreException( wfMessage( 'score-noogghandler' ) ); |
230 | 257 | } |
231 | | - if ( $options['vorbis'] && ( $options['override_ogg'] !== false ) ) { |
| 258 | + if ( $options['generate_vorbis'] && ( $options['override_ogg'] !== false ) ) { |
232 | 259 | throw new ScoreException( wfMessage( 'score-vorbisoverrideogg' ) ); |
233 | 260 | } |
234 | | - |
235 | | - /* Midi rendering? */ |
236 | | - if ( array_key_exists( 'midi', $args ) ) { |
237 | | - $options['midi'] = $args['midi']; |
238 | | - } else { |
239 | | - $options['midi'] = false; |
| 261 | + if ( $options['generate_vorbis'] ) { |
| 262 | + $options['ogg_path'] = "{$options['image_path_prefix']}.ogg"; |
| 263 | + $options['ogg_url'] = "{$options['image_url_prefix']}.ogg"; |
240 | 264 | } |
241 | 265 | |
242 | 266 | /* Raw rendering? */ |
— | — | @@ -257,74 +281,78 @@ |
258 | 282 | * Generates the HTML code for a score tag. |
259 | 283 | * |
260 | 284 | * @param $parser Parser MediaWiki parser. |
261 | | - * @param $code score code. |
262 | | - * @param $options array of music rendering options. Available options keys are: |
263 | | - * * lang: score language, |
264 | | - * * vorbis: whether to create an Ogg/Vorbis file in an OggHandler, |
265 | | - * * midi: whether to link to a MIDI file, |
266 | | - * * raw: whether to assume raw LilyPond code. |
| 285 | + * @param $code string Score code. |
| 286 | + * @param $options array of rendering options. |
| 287 | + * The options keys are: |
| 288 | + * * factory_directory: string Path to directory in which files |
| 289 | + * may be generated without stepping on someone else's |
| 290 | + * toes. The directory may not exist yet. Required. |
| 291 | + * * generate_vorbis: bool Whether to create an Ogg/Vorbis file in |
| 292 | + * an OggHandler. If set to true, the override_ogg option |
| 293 | + * must be set to false. Required. |
| 294 | + * * image_path_prefix: string Prefix to the local image path. |
| 295 | + * Required. |
| 296 | + * * image_url_prefix: string Prefix to the image URL. Required. |
| 297 | + * * lang: string Score language. Required. |
| 298 | + * * link_midi: bool Whether to link to a MIDI file. Required. |
| 299 | + * * midi_path: string Local MIDI path. Required if the link_midi |
| 300 | + * option is set to true, ignored otherwise. |
| 301 | + * * midi_url: string MIDI URL. Required if the link_midi option |
| 302 | + * is set to true, ignored otherwise. |
| 303 | + * * ogg_name: string Name of the OGG file. Required if the |
| 304 | + * override_ogg option is set to true, ignored otherwise. |
| 305 | + * * ogg_path: string Local Ogg/Vorbis path. Required if the |
| 306 | + * generate_vorbis option is set to true, ignored |
| 307 | + * otherwise. |
| 308 | + * * ogg_url: string Ogg/Vorbis URL. Required if the |
| 309 | + * generate_vorbis option is set to true, ignored |
| 310 | + * otherwise. |
| 311 | + * * override_ogg: bool Whether to generate a wikilink to a |
| 312 | + * user-provided OGG file. If set to true, the vorbis |
| 313 | + * option must be set to false. Required. |
| 314 | + * * raw: bool Whether to assume raw LilyPond code. Ignored if the |
| 315 | + * language is not lilypond, required otherwise. |
267 | 316 | * |
268 | 317 | * @return string HTML. |
269 | 318 | * |
270 | 319 | * @throws ScoreException if an error occurs. |
271 | 320 | */ |
272 | 321 | private static function generateHTML( &$parser, $code, $options ) { |
273 | | - global $wgUploadDirectory, $wgUploadPath, $wgTmpDirectory, $wgOut; |
| 322 | + global $wgOut; |
274 | 323 | |
275 | 324 | $prof = new ScopedProfiling( __METHOD__ ); |
276 | 325 | |
277 | | - /* Various paths and file names */ |
278 | | - $cacheName = md5( $code ); /* always use MD5 of $code, regardless of language */ |
279 | | - $cacheSubdir = "{$cacheName[0]}/{$cacheName[0]}{$cacheName[1]}"; |
280 | | - $lilypondDir = $wgUploadDirectory . '/' . self::LILYPOND_DIR_NAME . '/' . $cacheSubdir; |
281 | | - $lilypondPath = $wgUploadPath . '/' . self::LILYPOND_DIR_NAME . '/' . $cacheSubdir; |
282 | | - $filePrefix = "$lilypondDir/$cacheName"; |
283 | | - $pathPrefix = "$lilypondPath/$cacheName"; |
284 | | - $midi = "$filePrefix.midi"; |
285 | | - $midiPath = "$pathPrefix.midi"; |
286 | | - $image = "$filePrefix.png"; |
287 | | - $imagePath = "$pathPrefix.png"; |
288 | | - $multiFormat = "$filePrefix-%d.png"; // for multi-page scores |
289 | | - $multiPathFormat = "$pathPrefix-%d.png"; |
290 | | - $multi1 = "$filePrefix-1.png"; |
291 | | - $ogg = "$filePrefix.ogg"; |
292 | | - $oggPath = "$pathPrefix.ogg"; |
293 | | - |
294 | | - /* Make sure $lilypondDir exists */ |
295 | | - if ( !file_exists( $lilypondDir ) ) { |
296 | | - $rc = wfMkdirParents( $lilypondDir, null, __METHOD__ ); |
297 | | - if ( !$rc ) { |
298 | | - throw new ScoreException( wfMessage( 'score-nooutput', self::LILYPOND_DIR_NAME ) ); |
299 | | - } |
300 | | - } |
301 | | - |
302 | | - /* Generate working environment data */ |
303 | | - $fuzz = md5( mt_rand() ); |
304 | | - $factoryDirectory = $wgTmpDirectory . "/MWLP.$fuzz"; |
305 | | - |
306 | 326 | try { |
307 | 327 | /* Generate PNG and MIDI files if necessary */ |
308 | | - if ( ( !file_exists( $image ) && !file_exists( $multi1 ) ) |
309 | | - || ( ( $options['midi'] || $options['vorbis'] ) && !file_exists( $midi ) ) ) { |
310 | | - self::generatePngAndMidi( $code, $options, $filePrefix, $factoryDirectory ); |
| 328 | + $imagePath = "{$options['image_path_prefix']}.png"; |
| 329 | + $multi1Path = "{$options['image_path_prefix']}-1.png"; |
| 330 | + if ( !$options['link_midi'] && $options['generate_vorbis'] && !file_exists( $options['ogg_path'] ) ) { |
| 331 | + /* We need a MIDI file to generate a Vorbis file */ |
| 332 | + $options['midi_path'] = "{$options['factory_directory']}/file.midi"; |
311 | 333 | } |
| 334 | + if ( ( !file_exists( $imagePath ) && !file_exists( $multi1Path ) ) |
| 335 | + || ( array_key_exists( 'midi_path', $options ) && !file_exists( $options['midi_path'] ) ) ) { |
| 336 | + self::generatePngAndMidi( $code, $options ); |
| 337 | + } |
312 | 338 | |
313 | 339 | /* Generate Ogg/Vorbis file if necessary */ |
314 | | - if ( $options['vorbis'] && !file_exists( $ogg ) ) { |
315 | | - self::generateOgg( $options, $filePrefix, $factoryDirectory ); |
| 340 | + if ( $options['generate_vorbis'] && !file_exists( $options['ogg_path'] ) ) { |
| 341 | + self::generateOgg( $options ); |
316 | 342 | } |
317 | 343 | |
318 | 344 | /* return output link(s) */ |
319 | | - if ( file_exists( $image ) ) { |
| 345 | + if ( file_exists( $imagePath ) ) { |
320 | 346 | $link = Html::rawElement( 'img', array( |
321 | | - 'src' => $imagePath, |
| 347 | + 'src' => "{$options['image_url_prefix']}.png", |
322 | 348 | 'alt' => $code, |
323 | 349 | ) ); |
324 | | - } elseif ( file_exists( $multi1 ) ) { |
| 350 | + } elseif ( file_exists( $multi1Path ) ) { |
| 351 | + $multiPathFormat = "{$options['image_path_prefix']}-%d.png"; |
| 352 | + $multiUrlFormat = "{$options['image_url_prefix']}-%d.png"; |
325 | 353 | $link = ''; |
326 | | - for ( $i = 1; file_exists( sprintf( $multiFormat, $i ) ); ++$i ) { |
| 354 | + for ( $i = 1; file_exists( sprintf( $multiPathFormat, $i ) ); ++$i ) { |
327 | 355 | $link .= Html::rawElement( 'img', array( |
328 | | - 'src' => sprintf( $multiPathFormat, $i ), |
| 356 | + 'src' => sprintf( $multiUrlFormat, $i ), |
329 | 357 | 'alt' => wfMessage( 'score-page' )->inContentLanguage()->numParams( $i )->plain() |
330 | 358 | ) ); |
331 | 359 | } |
— | — | @@ -332,14 +360,20 @@ |
333 | 361 | /* No images; this may happen in raw mode or when the user omits the score code */ |
334 | 362 | throw new ScoreException( wfMessage( 'score-noimages' ) ); |
335 | 363 | } |
336 | | - if ( $options['midi'] ) { |
337 | | - $link = Html::rawElement( 'a', array( 'href' => $midiPath ), $link ); |
| 364 | + if ( $options['link_midi'] ) { |
| 365 | + $link = Html::rawElement( 'a', array( 'href' => $options['midi_url'] ), $link ); |
338 | 366 | } |
339 | | - if ( $options['vorbis'] ) { |
| 367 | + if ( $options['generate_vorbis'] ) { |
340 | 368 | try { |
341 | 369 | $oh = new OggHandler(); |
342 | 370 | $oh->setHeaders( $wgOut ); |
343 | | - $oad = new OggAudioDisplay( new UnregisteredLocalFile( false, false, $ogg ), $oggPath, self::DEFAULT_PLAYER_WIDTH, 0, 0, $oggPath, false ); |
| 371 | + $oad = new OggAudioDisplay( |
| 372 | + new UnregisteredLocalFile( false, false, $options['ogg_path'] ), |
| 373 | + $options['ogg_url'], |
| 374 | + self::DEFAULT_PLAYER_WIDTH, 0, 0, |
| 375 | + $options['ogg_url'], |
| 376 | + false |
| 377 | + ); |
344 | 378 | $link .= $oad->toHtml( array( 'alt' => $code ) ); |
345 | 379 | } catch ( Exception $e ) { |
346 | 380 | throw new ScoreException( wfMessage( 'score-novorbislink', $e->getMessage() ), 0, $e ); |
— | — | @@ -347,49 +381,53 @@ |
348 | 382 | } |
349 | 383 | if ( $options['override_ogg'] !== false ) { |
350 | 384 | try { |
351 | | - $link .= $parser->recursiveTagParse( "[[File:{$options['override_ogg']}]]" ); |
| 385 | + $link .= $parser->recursiveTagParse( "[[File:{$options['ogg_name']}]]" ); |
352 | 386 | } catch ( Exception $e ) { |
353 | 387 | throw new ScoreException( wfMessage( 'score-novorbislink', $e->getMessage() ), 0, $e ); |
354 | 388 | } |
355 | 389 | } |
356 | 390 | } catch ( Exception $e ) { |
357 | | - self::eraseFactory( $factoryDirectory ); |
| 391 | + self::eraseFactory( $options['factory_directory'] ); |
358 | 392 | throw $e; |
359 | 393 | } |
360 | 394 | |
| 395 | + self::eraseFactory( $options['factory_directory'] ); |
| 396 | + |
361 | 397 | return $link; |
362 | 398 | } |
363 | 399 | |
364 | 400 | /** |
365 | 401 | * Generates score PNG file(s) and possibly a MIDI file. |
366 | 402 | * |
367 | | - * @param $code string score code. |
368 | | - * @param $options array rendering options, see Score::generateHTML() for explanation. |
369 | | - * @param $filePrefix string prefix for the generated files. |
370 | | - * @param $factoryDirectory string directory of the working environment. |
| 403 | + * @param $code string Score code. |
| 404 | + * @param $options array Rendering options. They are the same as for |
| 405 | + * Score::generateHTML() with the exception that the link_midi |
| 406 | + * option is ignored and the midi file is generated if the |
| 407 | + * midi_path option is present. |
371 | 408 | * |
372 | 409 | * @throws ScoreException on error. |
373 | 410 | */ |
374 | | - private static function generatePngAndMidi( $code, $options, $filePrefix, $factoryDirectory ) { |
| 411 | + private static function generatePngAndMidi( $code, $options ) { |
375 | 412 | global $wgScoreLilyPond, $wgScoreTrim; |
376 | 413 | |
377 | 414 | $prof = new ScopedProfiling( __METHOD__ ); |
378 | 415 | |
379 | 416 | /* Various filenames */ |
380 | | - $ly = "$filePrefix.ly"; |
381 | | - $midi = "$filePrefix.midi"; |
382 | | - $image = "$filePrefix.png"; |
383 | | - $multiFormat = "$filePrefix-%d.png"; |
| 417 | + $image = "{$options['image_path_prefix']}.png"; |
| 418 | + $multiFormat = "{$options['image_path_prefix']}-%d.png"; |
384 | 419 | |
385 | 420 | /* delete old files if necessary */ |
386 | | - self::cleanupFile( $midi ); |
| 421 | + if ( array_key_exists( 'midi_path', $options ) ) { |
| 422 | + self::cleanupFile( $options['midi_path'] ); |
| 423 | + } |
387 | 424 | self::cleanupFile( $image ); |
388 | 425 | for ( $i = 1; file_exists( $f = sprintf( $multiFormat, $i ) ); ++$i ) { |
389 | 426 | self::cleanupFile( $f ); |
390 | 427 | } |
391 | 428 | |
392 | 429 | /* Create the working environment */ |
393 | | - self::createFactory( $factoryDirectory ); |
| 430 | + $factoryDirectory = $options['factory_directory']; |
| 431 | + self::createDirectory( $factoryDirectory, 0700 ); |
394 | 432 | $factoryLy = "$factoryDirectory/file.ly"; |
395 | 433 | $factoryMidi = "$factoryDirectory/file.midi"; |
396 | 434 | $factoryImage = "$factoryDirectory/file.png"; |
— | — | @@ -397,30 +435,23 @@ |
398 | 436 | $factoryMultiFormat = "$factoryDirectory/file-%d.png"; |
399 | 437 | $factoryMultiTrimmedFormat = "$factoryDirectory/file-%d-trimmed.png"; |
400 | 438 | |
401 | | - /* Determine which LilyPond code to use */ |
| 439 | + /* Generate LilyPond input file */ |
402 | 440 | if ( $options['lang'] == 'lilypond' ) { |
403 | 441 | if ( $options['raw'] ) { |
404 | 442 | $lilypondCode = $code; |
405 | 443 | } else { |
406 | 444 | $lilypondCode = self::embedLilypondCode( $code, $options ); |
407 | 445 | } |
408 | | - } else { |
409 | | - wfSuppressWarnings(); |
410 | | - $lilypondCode = file_get_contents( $ly ); // may legitimately fail |
411 | | - wfRestoreWarnings(); |
412 | | - if ( $lilypondCode === false ) { |
413 | | - /* (re-)generate .ly file */ |
414 | | - $lilypondCode = self::generateLilypond( $code, $options, $filePrefix, $factoryDirectory ); |
415 | | - } |
416 | | - } |
417 | | - |
418 | | - /* generate lilypond output files in working environment */ |
419 | | - if ( !file_exists( $factoryLy ) ) { |
420 | 446 | $rc = file_put_contents( $factoryLy, $lilypondCode ); |
421 | 447 | if ( $rc === false ) { |
422 | 448 | throw new ScoreException( wfMessage( 'score-noinput', $factoryLy ) ); |
423 | 449 | } |
| 450 | + } else { |
| 451 | + $options['lilypond_path'] = $factoryLy; |
| 452 | + self::generateLilypond( $code, $options ); |
424 | 453 | } |
| 454 | + |
| 455 | + /* generate lilypond output files in working environment */ |
425 | 456 | $oldcwd = getcwd(); |
426 | 457 | if ( $oldcwd === false ) { |
427 | 458 | throw new ScoreException( wfMessage( 'score-getcwderr' ) ); |
— | — | @@ -446,7 +477,7 @@ |
447 | 478 | if ( $rc2 != 0 ) { |
448 | 479 | self::throwCallException( wfMessage( 'score-compilererr' ), $output ); |
449 | 480 | } |
450 | | - if ( ( $options['midi'] || $options['vorbis'] ) && !file_exists( $factoryMidi ) ) { |
| 481 | + if ( array_key_exists( 'midi_path', $options ) && !file_exists( $factoryMidi ) ) { |
451 | 482 | throw new ScoreException( wfMessage( 'score-nomidi' ) ); |
452 | 483 | } |
453 | 484 | |
— | — | @@ -465,25 +496,25 @@ |
466 | 497 | |
467 | 498 | /* move files to proper places */ |
468 | 499 | $rc = true; |
469 | | - if ( file_exists( $factoryMidi ) ) { |
470 | | - $rc = $rc && rename( $factoryMidi, $midi ); |
| 500 | + if ( array_key_exists( 'midi_path', $options ) ) { |
| 501 | + self::renameFile( $factoryMidi, $options['midi_path'] ); |
471 | 502 | } |
472 | 503 | if ( file_exists( $factoryImageTrimmed ) ) { |
473 | | - $rc = $rc && rename( $factoryImageTrimmed, $image ); |
| 504 | + self::renameFile( $factoryImageTrimmed, $image ); |
474 | 505 | } |
475 | 506 | for ( $i = 1; file_exists( $f = sprintf( $factoryMultiTrimmedFormat, $i ) ); ++$i ) { |
476 | | - $rc = $rc && rename( $f, sprintf( $multiFormat, $i ) ); |
| 507 | + self::renameFile( $f, sprintf( $multiFormat, $i ) ); |
477 | 508 | } |
478 | | - if ( !$rc ) { |
479 | | - throw new ScoreException( wfMessage( 'score-renameerr' ) ); |
480 | | - } |
481 | 509 | } |
482 | 510 | |
483 | 511 | /** |
484 | 512 | * Embeds simple LilyPond code in a score block. |
485 | 513 | * |
486 | | - * @param $lilypondCode string simple LilyPond code. |
487 | | - * @param $options array rendering options, see Score::generateHTML() for explanation. |
| 514 | + * @param $lilypondCode string Simple LilyPond code. |
| 515 | + * @param $options array Rendering options. The are the same as for |
| 516 | + * Score::generateHTML() except that the link_midi option is |
| 517 | + * ignored and a MIDI block is embedded if and only if the |
| 518 | + * midi_path option is present. |
488 | 519 | * |
489 | 520 | * @return string Raw lilypond code. |
490 | 521 | * |
— | — | @@ -509,7 +540,7 @@ |
510 | 541 | . "\\score {\n" |
511 | 542 | . $lilypondCode |
512 | 543 | . "\t\\layout { }\n" |
513 | | - . ( ( $options['midi'] || $options['vorbis'] ) ? "\t\\midi { }\n" : "" ) |
| 544 | + . ( array_key_exists( 'midi_path', $options ) ? "\t\\midi { }\n" : "" ) |
514 | 545 | . "}\n"; |
515 | 546 | return $raw; |
516 | 547 | } |
— | — | @@ -517,25 +548,23 @@ |
518 | 549 | /** |
519 | 550 | * Generates an Ogg/Vorbis file from a MIDI file using timidity. |
520 | 551 | * |
521 | | - * @param $options array rendering options, see Score::generateHTML() for explanation. |
522 | | - * @param $filePrefix string prefix for the generated Ogg file. |
523 | | - * @param $factoryDirectory string directory of the working environment. |
| 552 | + * @param $options array Rendering options. They are the same as for |
| 553 | + * Score::generateHTML(), except that the midi_path option must |
| 554 | + * be present. |
524 | 555 | * |
525 | 556 | * @throws ScoreException if an error occurs. |
526 | 557 | */ |
527 | | - private static function generateOgg( $options, $filePrefix, $factoryDirectory ) { |
| 558 | + private static function generateOgg( $options ) { |
528 | 559 | global $wgScoreTimidity; |
529 | 560 | |
530 | 561 | $prof = new ScopedProfiling( __METHOD__ ); |
531 | 562 | |
532 | 563 | /* Working environment */ |
533 | | - self::createFactory( $factoryDirectory ); |
534 | | - $factoryOgg = "$factoryDirectory/file.ogg"; |
535 | | - $midi = "$filePrefix.midi"; |
536 | | - $ogg = "$filePrefix.ogg"; |
| 564 | + self::createDirectory( $options['factory_directory'], 0700 ); |
| 565 | + $factoryOgg = "{$options['factory_directory']}/file.ogg"; |
537 | 566 | |
538 | 567 | /* Delete old file if necessary */ |
539 | | - self::cleanupFile( $ogg ); |
| 568 | + self::cleanupFile( $options['ogg_path'] ); |
540 | 569 | |
541 | 570 | /* Run timidity */ |
542 | 571 | if ( !is_executable( $wgScoreTimidity ) ) { |
— | — | @@ -544,7 +573,7 @@ |
545 | 574 | $cmd = wfEscapeShellArg( $wgScoreTimidity ) |
546 | 575 | . ' -Ov' // Vorbis output |
547 | 576 | . ' --output-file=' . wfEscapeShellArg( $factoryOgg ) |
548 | | - . ' ' . wfEscapeShellArg( $midi ) |
| 577 | + . ' ' . wfEscapeShellArg( $options['midi_path'] ) |
549 | 578 | . ' 2>&1'; |
550 | 579 | $output = wfShellExec( $cmd, $rc ); |
551 | 580 | if ( ( $rc != 0 ) || !file_exists( $factoryOgg ) ) { |
— | — | @@ -552,36 +581,30 @@ |
553 | 582 | } |
554 | 583 | |
555 | 584 | /* Move resultant file to proper place */ |
556 | | - $rc = rename( $factoryOgg, $ogg ); |
557 | | - if ( !$rc ) { |
558 | | - throw new ScoreException( wfMessage( 'score-renameerr' ) ); |
559 | | - } |
| 585 | + self::renameFile( $factoryOgg, $options['ogg_path'] ); |
560 | 586 | } |
561 | 587 | |
562 | 588 | /** |
563 | 589 | * Generates LilyPond code. |
564 | 590 | * |
565 | | - * @param $code string score code. |
566 | | - * @param $options array rendering options, see Score::generateHTML() for explanation. |
567 | | - * @param $filePrefix string prefix for the generated file. |
568 | | - * @param $factoryDirectory string directory of the working environment. |
| 591 | + * @param $code string Score code. |
| 592 | + * @param $options array Rendering options. They are the same as for |
| 593 | + * Score::generateHTML(), with the following addition: |
| 594 | + * * lilypond_path: path to the LilyPond file that is to be |
| 595 | + * generated. |
569 | 596 | * |
570 | | - * @return the generated LilyPond code. |
571 | | - * |
572 | 597 | * @throws ScoreException if an error occurs. |
573 | 598 | */ |
574 | | - private static function generateLilypond( $code, $options, $filePrefix, $factoryDirectory ) { |
| 599 | + private static function generateLilypond( $code, $options ) { |
575 | 600 | $prof = new ScopedProfiling( __METHOD__ ); |
576 | 601 | |
577 | | - $ly = "$filePrefix.ly"; |
578 | | - |
579 | 602 | /* Delete old file if necessary */ |
580 | | - self::cleanupFile( $ly ); |
| 603 | + self::cleanupFile( $options['lilypond_path'] ); |
581 | 604 | |
582 | 605 | /* Generate LilyPond code by score language */ |
583 | 606 | switch ( $options['lang'] ) { |
584 | 607 | case 'ABC': |
585 | | - $lilypondCode = self::generateLilypondFromAbc( $code, $factoryDirectory ); |
| 608 | + self::generateLilypondFromAbc( $code, $options ); |
586 | 609 | break; |
587 | 610 | case 'lilypond': |
588 | 611 | throw new MWException( 'lang="lilypond" in ' . __METHOD__ . ". This should not happen.\n" ); |
— | — | @@ -589,33 +612,24 @@ |
590 | 613 | throw new MWException( 'Unknown score language in ' . __METHOD__ . ". This should not happen.\n" ); |
591 | 614 | } |
592 | 615 | |
593 | | - /* Create LilyPond file and return the code */ |
594 | | - $rc = file_put_contents( $ly, $lilypondCode ); |
595 | | - if ( $rc === false ) { |
596 | | - self::debug( "Unable to write LilyPond code to $ly.\n" ); |
597 | | - } |
598 | | - |
599 | | - return $lilypondCode; |
600 | 616 | } |
601 | 617 | |
602 | 618 | /** |
603 | 619 | * Runs abc2ly, creating the LilyPond input file. |
604 | 620 | * |
605 | | - * $code ABC code. |
606 | | - * $factoryDirectory Working environment. As a side-effect, the |
607 | | - * LilyPond input file is created as "file.ly" in this directory. |
| 621 | + * @param $code string ABC code. |
| 622 | + * @param $options array Rendering options. They are the same as for |
| 623 | + * Score::generateLilypond(). |
608 | 624 | * |
609 | | - * @param $code string |
610 | | - * @param $factoryDirectory string |
611 | | - * @return string the generated LilyPond code. |
612 | | - * |
613 | 625 | * @throws ScoreException if the conversion fails. |
614 | 626 | */ |
615 | | - private function generateLilypondFromAbc( $code, $factoryDirectory ) { |
| 627 | + private static function generateLilypondFromAbc( $code, $options ) { |
616 | 628 | global $wgScoreAbc2Ly; |
617 | 629 | |
618 | 630 | $prof = new ScopedProfiling( __METHOD__ ); |
619 | 631 | |
| 632 | + /* File names */ |
| 633 | + $factoryDirectory = $options['factory_directory']; |
620 | 634 | $factoryAbc = "$factoryDirectory/file.abc"; |
621 | 635 | $factoryLy = "$factoryDirectory/file.ly"; |
622 | 636 | |
— | — | @@ -636,13 +650,9 @@ |
637 | 651 | . ' ' . wfEscapeShellArg( $factoryAbc ) |
638 | 652 | . ' 2>&1'; |
639 | 653 | $output = wfShellExec( $cmd, $rc ); |
640 | | - if ( $rc != 0 ) { |
| 654 | + if ( ( $rc != 0 ) || !file_exists( $factoryLy ) ) { |
641 | 655 | self::throwCallException( wfMessage( 'score-abcconversionerr' ), $output ); |
642 | 656 | } |
643 | | - if ( !file_exists( $factoryLy ) ) { |
644 | | - /* Occasionally, abc2ly will return exit code 0 but not create an output file */ |
645 | | - self::throwCallException( wfMessage( 'score-abcconversionerr' ), $output ); |
646 | | - } |
647 | 657 | |
648 | 658 | /* The output file has a tagline which should be removed in a wiki context */ |
649 | 659 | $lyData = file_get_contents( $factoryLy ); |
— | — | @@ -658,7 +668,8 @@ |
659 | 669 | throw new ScoreException( wfMessage( 'score-noinput', $factoryLy ) ); |
660 | 670 | } |
661 | 671 | |
662 | | - return $lyData; |
| 672 | + /* Move resultant file to proper place */ |
| 673 | + self::renameFile( $factoryLy, $options['lilypond_path'] ); |
663 | 674 | } |
664 | 675 | |
665 | 676 | /** |
— | — | @@ -708,6 +719,23 @@ |
709 | 720 | } |
710 | 721 | |
711 | 722 | /** |
| 723 | + * Renames a file, making sure that the target directory exists. |
| 724 | + * Do not use with uncanonicalised paths. |
| 725 | + * |
| 726 | + * @param $oldname string Old file name. |
| 727 | + * @param $newname string New file name. |
| 728 | + * |
| 729 | + * @throws ScoreException if an error occurs. |
| 730 | + */ |
| 731 | + private static function renameFile( $oldname, $newname ) { |
| 732 | + self::createDirectory( dirname( $newname ) ); |
| 733 | + $rc = rename( $oldname, $newname ); |
| 734 | + if ( !$rc ) { |
| 735 | + throw new ScoreException( wfMessage( 'score-renameerr' ) ); |
| 736 | + } |
| 737 | + } |
| 738 | + |
| 739 | + /** |
712 | 740 | * Deletes a file if it exists. |
713 | 741 | * |
714 | 742 | * @param $path string path to the file to be deleted. |