Unable to Downsample audio file in CGI perl script using sox - perl

I am working on a cgi script where I get an uploaded an audio file, downsample it to 8000Hz and then get it recognised later.
I am facing an error while downsampling the file. The code for downsampling goes like:
1) Code for File Upload:
use CGI;
use strict;
use File::Copy qw(copy);
use CGI::Carp 'fatalsToBrowser';
my $PROGNAME = "file_upload.cgi";
my $cgi = new CGI();
print "Content-type: text/html\n\n";
my $upfile = $cgi->param('upfile');
# Get the basename in case we want to use it.
my $basename = GetBasename($upfile);
no strict 'refs';
if (! open(OUTFILE, ">../cgi-bin/upload/".$basename) ) {
print "Can't open for writing - $!";
exit(-1);
}
2)Code for downsample:
my $source_file="/var/www/cgi-bin/upload/$upfile";
system("sox $source_file -r 8000 /var/www/cgi-bin/upload/temp.wav".";"."mv /var/www/cgi-bin/upload/temp.wav $source_file");
where:
source_file is the path for uploaded audio file
$upfile is the name of the uploaded wav file
temp.wav is the temporary downsampled file which is overwritten on the original file using mv command
Error
sox FAIL formats: can't open input file `/var/www/cgi-bin/upload/file1.wav': WAVE: RIFF header not found
file1.wav is the file I uploaded
Please help me understand why the sox command is not executing despite it being correctly written?

This isn't really an answer to your question as we don't have enough information yet.
Have you tried running the command from your Unix command line? I'd assume you get the same error. What do you get if you run file on the file that you have saved? How big is the file before and after you upload it?
You don't show the code that writes the uploaded file. I suspect there's a bug in that. If you add that to your question, we could help you find it.
Where is GetBasename() defined? Can we see the code?
Your sox command seems strange. You're running sox on a file called temp.wav and then copying that file over your uploaded file. Perhaps there are a couple of steps that you aren't telling us.
Some other suggestions for improvement:
Use cgi->new, not new CGI. The latter has some strange corner cases that you will have real problems debugging if you ever come across them.
If you're loading the CGI module, then why not use its header method instead of writing your own (technically incorrect) header.
no strict 'refs' is a really bad idea (and, as far as I can see, isn't needed here).
Please use the three-arg version of open() and lexical filehandles
open my $out_fh, '>', "../cgi-bin/upload/$basename"
Include the file path in your error message
my $file = "../cgi-bin/upload/$basename";
if (!open my $out_fh, '>', $file) {
print "Can't open file '$file' for writing - $!";
exit(-1);
}
You are loading the File::Copy module, but then moving your file using a shell command.
Allowing random users to upload files into a directory under your cgi-bin directory is a massive potential security hole. You should find another directory to store the uploaded files.
Oh, and then there's the whole - why on Earth would you be writing CGI programs in 2017!

The issue is resolved. The reason why I was having problem executing the sox and copy commands was because of where I was placing the two commands in code. Basically a beginners error. So I was opening the file as mentioned in the problem statement. I put the copy and sox commands for execution before I closed the filehandler and hence they were not getting executed successfully.

Related

Reading a file in perl with CGI?

The code is supposed to read a file in perl but is supposed to be modified with CGI. I have looked over several tutorials, and think it is the file path, but cannot get it to work. Any ideas? I'm newer to CGI and perl. Trying to run it through html.
#!/usr/bin/perl
use warnings;
use strict;
my $filename = '/home/cisstudent/test.txt';
open(FH, '<', $filename) or die $!;
print("File $filename opened successfully!\n");
close(FH);
The first line of your posted code is incorrect - it should be #!/usr/bin/perl - /usr, not /user.
If that doesn't fix the script (or if it's just a typo in the post, rather than in your actual code), then you'll need to provide additional details, such as how you're running the script and the error message received when you attempt to run it.

Failing to open Excel files for writing

I am using open to write data into an Excel file. This is working fine with .txt files, but with .xls files it always fails.
This is the code I am writing
$filename = "abc.xls";
$fhandle = "ABC";
open( $fhandle, ">$filename" ) || die "cannot open file $filename";
The same code executes fine in another environment which has an older Perl version.
I need help on how can I fix this.
Found the problem to the issue. The environment I was trying to create these files in already had the files with the same name but did not have permissions for me to edit those files. I deleted the old files and it worked like a charm.
Thanks a lot for your help guys.!!
Shivam
A file handle is a file handle. Not a string.
Also, you are typing the filename in the open call. Why don't you use the $filename variable you so neatly created two lines earlier?
I would try something like this:
$filename = "abc.xls";
open($fhandle, ">$filename") || die "cannot open file $filename";
Also, this xls file: Is it open in excel when you try to run the script? Excel will stop you from opening it in another application or script if you do.
Furthermore : Please use strict and warnings. They will make your life much better.

CGI log file empty

I feel like a real novice with this question, but sometimes huge experience can have a bad day.
I have this CGI script for file upload in file chunks. It is correctly uploading my files just fine, however I can't get a log file to write log data even though it used to work perfectly. The log file is zero bytes.
I added a couple of lines to greatly simplify debug at the start of the script. They are listed below. They open a new log file, write a simple line of text, then close it.
I figured it can't be a flush problem as I'm closing the file. It must be opening the file because the file exists, just zero bytes. It must be running the CGI script, because my file does not upload otherwise. And it has permission to write to /tmp, as the file is created. Also there are no errors in /etc/httpd/logs/errorlog
open(LOGFILE, ">", "/tmp/olss2.log") or die "Can't open log file: $!";
$| = 1;
print LOGFILE "New Log Entry Started";
close LOGFILE;

Perl automated file testing

So I'm taking a Perl programming class and our teacher gave us our first assignment with very little talk about how to actually program perl. Here's exactly what our teacher assigned:
"You should write a script (you may name you script whatever you deem appropriate) that accepts 3 filenames as arguments. The first filename corresponds to the program source code written in C++. The second filename corresponds to an input file that is to be used by the C++ program listed as the first filename. The third filename corresponds to a text file that contains the expected, correct output for the program in question. A directory path may be provided with any of the filenames.
If the program doesn’t require an input file, the second parameter on the command line should be the filename "/dev/null".
All of the files - the program source, the input file, and expected output file - should be copied to a scratch test directory before any of them are used by the script so that there is little chance that the originals will be modified by the test procedure. If the scratch directory doesn’t exist, your script should create one as a subdirectory of the current working directory.
Your script should then compile and link the scratch copy of the source using the GNU g++ compiler. The script should then run the program – saving the output to a temporary file stored in the scratch test directory.
After running the program, your script should then use the UNIX command diff to compare the actual output generated in the previous step with the expected output file and report either that the output conforms to specifications or report any differences as reported by the diff program.
After completion, your script should remove all of the temporary copies and scratch files. Do not remove the original program, the original input file, the original expected output file, or the scratch directory itself."
I have this so far:
#!/usr/bin/perl -w
use strict;
my ($line, $program, $input, $output);
print "Give the program, input, and standart output for testing. ";
$line = <>;
chomp $line;
($program, $input, $output) = split/\s+/, $line; # split/\s+/ is to separate spaces from the input
my($o_test) = $output + "_test";
print "$program ";
print "$input ";
print "$output ";
system("mkdir test_scratch") == 0
or die "failed to create test_scratch. exiting...."
system("cp $program, /test_scratch/"); # error
system("cp $input, /test_scratch/");
system("cp $output, /test_scratch/");
system("cd test_scratch");
system("g++ $program");
system("chmod +x a.out");
system("./a.out < $input > $o_test");
my($DIFF) = system("diff $output $o_test") # error
if[ $DIFF != ""]
print ("Output conforms to specifications."); # error
then
print ("$DIFF");
system("cd ..");
I'm getting errors at the # in code. I don't even know how to do the "/dev/null". I've spent a lot of time looking things up online and searching stackoverflow, but I just don't know what else to do. I realize this is an extremely long question but I don't know what else to do. Thank you for ANY help you can give me.
There are several modules which can help you here. The first one I would recommend is ExtUtils::CBuilder which can manage a build process for you. Then you might also use File::Copy for moving things intot the temporary folder, and even File::chdir for managing the working directory. Since the prof specifies that you should use diff perhaps you should, but there are modules which do that task, or you could use Test::More to check that the output is what is expected.
Just for future reference, this is how I would accomplish a similar task (don't keep temp dir, don't need diff):
#!/usr/bin/env perl
use strict;
use warnings;
use File::chdir;
use File::Temp;
use File::Copy;
use File::Basename;
use File::Slurp;
use ExtUtils::CBuilder;
use Test::More tests => 1;
die "Not enough inputs\n" unless #ARGV >= 3;
# create a temporary directory
my $temp = File::Temp->newdir;
# copy all arguments to that temporary directory
copy $_, $temp for #ARGV;
# store only the filename (not path) of each argument
my ($cpp_file, $in_file, $expected_file) = map { scalar basename $_ } #ARGV;
# change working directory to temporary one (via File::chdir)
local $CWD = $temp;
# build the executable
my $builder = ExtUtils::CBuilder->new(config => {cc => 'g++'});
my $obj = $builder->compile( source => $cpp_file, 'C++' => 1 );
my $exe = $builder->link_executable( objects => 'hello.o' );
# run the executable
my $output = `./$exe $in_file`;
# read in the expected file
my $expected = read_file $expected_file;
# test the resulting output
is $output, $expected, 'Got expected output from executable';
Note that you may need to be careful with newlines on the output and expected files.
I'm getting errors at the # in code.
The easy fix for this error is to remove the comma. The standard Linux cp command doesn't take a comma between the source and destination. You might also consider File::Copy to copy files in Perl.
I don't even know how to do the "/dev/null".
/dev/null is a standard Linux file(*), which points to emptiness. You can write bytes to this file, and they disappear. You can try to read bytes from this file, and nothing's ever there.
This leaves you with two options:
Just use /dev/null directly, and the program will be unable to read anything from an empty byte stream.
Check if the input file is equal to /dev/null, and don't give the C++ program an input value if the input is /dev/null.
Either method would work. If you copy /dev/null to a directory, you get a new zero-length file called 'null', which is a perfectly valid empty input file for the C++ program.
(*) No, /dev/null is not a file in the sense you're used to if you come from the Windows world. It doesn't exist on disk - never has and never will. But the classic Unix philosophy is to make every data source a file, even if that file doesn't exist on disk. As an example, see the Linux /proc filesystem, which allows you to see information about CPUs, processes, etc. in a directory tree structure. That philosophy held up somewhat until the sockets API took a completely different route. If you're looking for an operating system that does make basically everything into a file, including network connections and the screen, look up Plan 9. I would not recommend doing your homework assignments on Plan 9 and expecting them to work on Linux, though.
N.B. Don't forget to check return codes from system, since if g++ fails to compile the C++ program there will be no way to run a compiled program that doesn't exist.

Calling Fortran program from Perl without saving input/output files

I'm using a Perl program to properly format user input into an input file for a Fortran program. The Fortran program creates an output file and error file. The Fortran program is called from Perl like:
system "/mydirectories/fortranexecutable $inputfile $outputfile $errorfile";
I am wondering if there is a way to call the Fortran executable without actually creating the input/output/error files and saving them to the disk before/after the Fortran program is called? I hope my question is clear and not something too obvious. I'm new to Perl and I've tried searching everywhere for this. Thanks for your help in advance.
If the Fortran code reads sequentially from and writes sequentially to already existing files but you would like to communicate with it in "real time" from the Perl code, then you can kind of get around using named pipes. They still exist as entries in the filesystem and can be opened as usual files by the Fortran code given their name but reading/writing from/to them works like piping.
In Perl you would do something like this (blatantly copied from this answer):
use File::Temp qw(tempdir);
use File::Spec::Functions qw(catfile);
use POSIX qw(mkfifo);
my $dir = tempdir(CLEANUP=>1);
my $inputfifo = catfile($dir, "input");
mkfifo($inputfifo, 0700) or die "mkfifo($inputfifo) failed: $!";
my $outputfifo = catfile($dir, "output");
mkfifo($outputfifo, 0700) or die "mkfifo(output$fifo) failed: $!";
my $errorfifo = catfile($dir, "error");
mkfifo($errorfifo, 0700) or die "mkfifo($errorfifo) failed: $!";
... open the FIFOs ...
system "/mydirectories/fortranexecutable $inputfifo $outputfifo $errorfifo";
... operate with the FIFOs to communicate with the Fortran code ...
... close FIFOs and remove $dir when finished ...
No. If Fortran program is written in such way that it takes $inputfile as a command line argument, reads data from it, and outputs $outputfile and $errorfile as a result, the only way to do it is through a file.
If you would prefer to pass input data to Fortran executable through standard input, Fortran source code would have to be modified to accomodate this kind of input.