PL/SQL Get Dimensions of JPEG Image - oracle

Support for Oracle Multimedia was dropped in Oracle 19c, so my code to extract dimensions from a JPEG image is throwing an error. Is there a workaround to this issue?
For Oracle 12, my code looked like this:
BEGIN
img := ORDSYS.ORDImage.init('FILE', my_dir, my_img_name);
img.setProperties();
w := img.getWidth();
h := img.getHeight();
EXCEPTION
WHEN OTHERS THEN
w := NULL;
h := NULL;
END;

Based on code found in a response to "Getting Image size of JPEG from its binary" (I'm not sure which language), I came up with this procedure:
PROCEDURE p_jpegstats(directory_in IN VARCHAR2,
filename_in IN VARCHAR2,
height_out OUT INTEGER,
width_out OUT INTEGER,
bpc_out OUT INTEGER, -- bits per channel
cps_out OUT INTEGER -- colors per component
) IS
file bfile;
pos INTEGER:=1;
h VARCHAR2(4);
w VARCHAR2(4);
mrkr VARCHAR2(2);
len VARCHAR2(4);
bpc VARCHAR2(2);
cps VARCHAR2(2);
-- Declare a quick helper procedure for readability
PROCEDURE next_byte(buf out varchar2, amt INTEGER:=1) IS
cnt INTEGER;
BEGIN
cnt := amt;
dbms_lob.read(file, cnt, pos, buf);
pos := pos + cnt;
END next_byte;
BEGIN
-- This code is based off of code found here: https://stackoverflow.com/a/48488655/3303651
-- Open the file
file := bfilename(directory_in, filename_in);
dbms_lob.fileopen(file);
-- Init the output variables in case something goes awry.
height_out := NULL;
width_out := NULL;
bpc_out := NULL;
cps_out := NULL;
LOOP
BEGIN
LOOP
next_byte(mrkr);
EXIT WHEN mrkr <> 'FF';
END LOOP;
CONTINUE WHEN mrkr = 'D8'; -- Start of image (SOI)
EXIT WHEN mrkr = 'D9'; -- End of image (EOI)
CONTINUE WHEN mrkr BETWEEN 'D0' AND 'D7';
CONTINUE WHEN mrkr = '01'; -- TEM
next_byte(len, 2);
IF mrkr = 'C0' THEN
next_byte(bpc); -- bits per channel
next_byte(h, 2); -- height
next_byte(w, 2); -- width
next_byte(cps); -- colors per component
EXIT;
END IF;
pos := pos + to_number(len, 'XXXX') - 2;
EXCEPTION WHEN OTHERS THEN EXIT; END;
END LOOP;
-- Write back the values we found
height_out := to_number(h, 'XXXX');
width_out := to_number(w, 'XXXX');
bpc_out := to_number(bpc, 'XX');
cps_out := to_number(cps, 'XX');
-- close out the file
dbms_lob.fileclose(file);
END p_jpegstats;
This will throw an error if the directory is invalid or the file can't be opened. If the outputs are NULL, then there was some other issue.
It's probably not the most efficient or elegant code (I'm not a pro with PL/SQL [yet!]), but it works. Here is an example usage:
DECLARE
h INTEGER;
w INTEGER;
bpc INTEGER;
cps INTEGER;
BEGIN
p_jpegstats('MY_DIR', 'my_image.jpg', h, w, bpc, cps);
DBMS_OUTPUT.PUT_LINE(w || ' x ' || h || ' ' || bpc || ' ' || cps);
END;
/
This ought to return something like
800 x 200 8 3
Edit: Removed unused variable.

Related

Error when decoding base 64 to blob

I am using the following function to convert a large base64 encoded file(image or voice) into a blob file and store it in the Oracle database (Oracle Database 11g Enterprise Edition Release 11.1.0.7.0 - Production).
I am able to store it and retrieve it but the image is getting corrupted. Only a potion of the image is getting retrieved. I tried using small images(11KB size) and it is working fine. But for larger images(88KB to 700KB) only a portion of the image is retrieved.
The problem is with the base-64 decoding. Earlier I was not able to get even the smaller image due to corruption, but when I increased the buffer size, it came fine. Now the buffer size is at its maximum at 32767 as its the maximum for varchar2 and raw.
Can anyone provide a suitable workaround or solution.
function decode_base64(p_clob_in in clob) return blob is
v_blob blob;
v_result blob;
v_offset integer;
v_buffer_size binary_integer := 32767; -- 24, 48, 3072
v_buffer_varchar varchar2(32767);
v_buffer_raw raw(32767);
begin
if p_clob_in is null then
return null;
end if;
dbms_lob.createtemporary(v_blob, true);
v_offset := 1;
for i in 1 .. ceil(dbms_lob.getlength(p_clob_in) / v_buffer_size)
loop
dbms_lob.read(p_clob_in, v_buffer_size, v_offset, v_buffer_varchar);
v_buffer_raw := utl_raw.cast_to_raw(v_buffer_varchar);
v_buffer_raw := utl_encode.base64_decode(v_buffer_raw);
dbms_lob.writeappend(v_blob, utl_raw.length(v_buffer_raw), v_buffer_raw);
v_offset := v_offset + v_buffer_size;
end loop;
v_result := v_blob;
dbms_lob.freetemporary(v_blob);
return v_result;
end decode_base64;
The code that i use to call the function and insert the blob into the table is given below...
PROCEDURE create_notes (
p_task_id IN NUMBER
,p_note_title IN VARCHAR2
,p_note_detail IN VARCHAR2
,p_attach_name IN VARCHAR2
,p_attachment IN CLOB
,p_attach_type IN VARCHAR2
,x_return_code OUT VARCHAR2
,x_return_message OUT VARCHAR2
)
IS
l_blob_data BLOB;
BEGIN
.
.
.
IF p_attachment IS NOT NULL THEN
SELECT incident_id INTO l_pk1_value FROM csf_ct_tasks where task_id = p_task_id;
l_blob_data := xx_utl_base64.decode_base64(p_attachment);
INSERT INTO fnd_lobs
(file_id, file_name, file_content_type, upload_date,
expiration_date, program_name, program_tag, file_data,
LANGUAGE, oracle_charset, file_format
)
VALUES (l_media_id, p_attach_name,p_attach_type, -- 'audio/mpeg','application/pdf','image/jpeg'
SYSDATE,
NULL, 'FNDATTCH', NULL, l_blob_data, --l_blob_data,EMPTY_BLOB ()
'US', 'UTF8', 'binary'
)
RETURNING file_data
INTO x_blob;
COMMIT;
END IF:
Attaching the original picture and its decoded version, below.
I got the below code from net. It worked like a charm. Dont know whats the problem with my old code though.
FUNCTION base64decode(p_clob CLOB)
RETURN BLOB
IS
l_blob BLOB;
l_raw RAW(32767);
l_amt NUMBER := 7700;
l_offset NUMBER := 1;
l_temp VARCHAR2(32767);
BEGIN
BEGIN
DBMS_LOB.createtemporary (l_blob, FALSE, DBMS_LOB.CALL);
LOOP
DBMS_LOB.read(p_clob, l_amt, l_offset, l_temp);
l_offset := l_offset + l_amt;
l_raw := UTL_ENCODE.base64_decode(UTL_RAW.cast_to_raw(l_temp));
DBMS_LOB.append (l_blob, TO_BLOB(l_raw));
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL;
END;
RETURN l_blob;
END;
I tried your function with a v_buffer_size of 8192 and it worked fine. I've tried several numbers smaller than 32767 and they all worked fine, so try something less than that.
For those who's still looking for a correct solution - you need to decode input data in multiples of 4. In case input contains non-base64 symbols (which are ignored by built-in function utl_encode.base64_decode), it might lead to incorrect results on large files.
I've found a lot of samples on the web which do not correctly decode, posting my code below
FUNCTION base64_decode(p_content CLOB) RETURN BLOB
IS
C_CHUNK_SIZE CONSTANT INTEGER := 12000; -- should be a multiple of 4
C_NON_BASE64_SYM_PATTERN CONSTANT VARCHAR2(20) := '[^A-Za-z0-9+/]';
l_chunk_buf VARCHAR2(12000);
l_chunk_b64_buf RAW(9000);
l_chunk_offset INTEGER := 1;
l_chunk_size INTEGER;
l_res BLOB;
FUNCTION get_next_full_base64_chunk(l_data CLOB, p_cur_pos IN OUT INTEGER, p_desired_size INTEGER, p_cur_size IN OUT INTEGER) RETURN VARCHAR2 IS
l_res VARCHAR2(12000);
l_tail_desired_size INTEGER;
BEGIN
l_res := dbms_lob.substr(l_data, p_desired_size, p_cur_pos);
p_cur_pos := p_cur_pos + p_desired_size;
IF l_res IS NULL THEN
RETURN NULL;
END IF;
l_res := regexp_replace(l_res, C_NON_BASE64_SYM_PATTERN, '');
p_cur_size := p_cur_size + length(l_res);
l_tail_desired_size := 4 - mod(p_cur_size, 4);
IF l_tail_desired_size = 4 THEN
RETURN l_res;
ELSE
RETURN l_res || get_next_full_base64_chunk(l_data, p_cur_pos, l_tail_desired_size, p_cur_size);
END IF;
END;
BEGIN
dbms_lob.createtemporary(l_res, false);
WHILE true
LOOP
l_chunk_size := 0;
l_chunk_buf := get_next_full_base64_chunk(p_content, l_chunk_offset, C_CHUNK_SIZE, l_chunk_size);
EXIT WHEN l_chunk_buf IS NULL;
l_chunk_b64_buf := utl_encode.base64_decode(utl_raw.cast_to_raw(l_chunk_buf));
dbms_lob.writeappend(l_res, utl_raw.length(l_chunk_b64_buf), l_chunk_b64_buf);
END LOOP;
RETURN l_res;
END;

'Reference to uninitialized composite' while trying to execute a stored procedure in Oracle with UDTs

There is this package and I am trying to execute using the code below. However I get an error
ORA-06530: Reference to uninitialized composite
Code:
DECLARE
StudyNum InputTyp := InputTyp ();
StudyDetails OutputTyp := OutputTyp ();
BEGIN
StudyNum.EXTEND;
StudyNum (1) := '9071';
my_package.my_procedure(StudyNum, StudyDetails);
END;
/
The package is created as below with user-defined datatypes as input & output params:
Create OR REPLACE Type InputTyp AS VARRAY(200) OF VARCHAR2 (1000);
CREATE TYPE OBJTYP AS OBJECT
(
A NUMBER,
B VARCHAR2 (1000),
C VARCHAR2 (100)
);
CREATE TYPE OutputTyp IS VARRAY (2000) OF OBJTYP;
/
CREATE OR REPLACE PACKAGE my_package
AS
PROCEDURE my_procedure(p_StudyNum IN InputTyp,p_StdyDtl OUT OutputTyp);
END my_package;
/
CREATE OR REPLACE PACKAGE BODY my_package
AS
PROCEDURE my_procedure(p_StudyNum IN InputTyp,p_StdyDtl OUT OutputTyp)
IS
i BINARY_INTEGER := 1;
j BINARY_INTEGER := 1;
CURSOR c_StudyTbl
IS
SELECT A, B, C
FROM my_table
WHERE Study_Number = p_StudyNum(i);
v_StudyTbl OBJTYP;
BEGIN
p_StdyDtl := OutputTyp ();
LOOP
-- This is the first cursor opened for each of the items in the list.
EXIT WHEN i > p_StudyNum.count;
OPEN c_StudyTbl;
LOOP
FETCH c_StudyTbl INTO v_StudyTbl;
EXIT WHEN c_StudyTbl%NOTFOUND;
p_StdyDtl.EXTEND ();
p_StdyDtl (j).A := v_StudyTbl.A;
p_StdyDtl (j).B := v_StudyTbl.B;
p_StdyDtl (j).C := v_StudyTbl.C;
j := j + 1;
END LOOP;
CLOSE c_StudyTbl;
i := i + 1;
END LOOP;
IF c_StudyTbl%ISOPEN
THEN
CLOSE c_StudyTbl;
END IF;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
NULL;
END;
END my_package;
/
you would either need to do :
p_StdyDtl(p_StdyDtl.last) := OBJTYP(null, null, null);
p_StdyDtl (j).A := v_StudyTbl.A;
p_StdyDtl (j).B := v_StudyTbl.B;
p_StdyDtl (j).C := v_StudyTbl.C;
or simpler:
p_StdyDtl(j) := OBJTYP(v_StudyTbl.A, v_StudyTbl.B, v_StudyTbl.C);
your code as-is fails because you've initialised the OutputTyp but not the objtyp part.
but as I said in the prior question of yours, the multiset avoids all of that.

How do I fix "numeric or value error" message?

I have been stuck with this issue now all morning. I actually saw this code here and decide to use it for our purpose here.
The issue that I am running into is that when we execute the code, sometimes it writes a file from db to the folder.
Other times, we get "numeric or value error"
Can any expert please help me fix it?
Here is the code I am using:
create or replace
PROCEDURE getfile(pfname VARCHAR2, display_name IN VARCHAR2)
IS
vblob BLOB;
vstart NUMBER := 1;
bytelen NUMBER := 32000;
len NUMBER;
my_vr RAW(32000);
x NUMBER;
v_name VARCHAR2(32760);
lv_str_len NUMBER;
l_output utl_file.file_type;
BEGIN
-- define output directory
--lv_str_len := Length(pfname);
--v_name := display_name||upper(substr(pfname,lv_str_len-3,lv_str_len));
v_name := display_name;
l_output := utl_file.Fopen('My_DIR', v_name, 'w', 32760);
-- get length of blob
SELECT dbms_lob.Getlength(FILENAME)
INTO len
FROM GENERAL.GUBFILE
WHERE gubfile_name = pfname;
-- dbms_output.put_line('Length: '||len);
-- save blob length
x := len;
-- select blob into variable
SELECT BLOBVALUE
INTO vblob
FROM FILES
WHERE filename = pfname;
-- if small enough for a single write
IF len < 32760 THEN
-- dbms_output.put_line('Single write ');
utl_file.Put_raw(l_output, vblob);
utl_file.Fflush(l_output);
ELSE -- write in pieces
-- dbms_output.put_line('multi write '||vstart);
vstart := 1;
WHILE vstart < len LOOP
dbms_lob.READ(vblob, bytelen, vstart, my_vr);
utl_file.Put_raw(l_output, my_vr);
utl_file.Fflush(l_output);
-- set the start position for the next cut
vstart := vstart + bytelen;
-- set the end position if less than 32000 bytes
x := x - bytelen;
IF x < 32000 THEN
bytelen := x;
END IF;
END LOOP;
END IF;
dbms_output.Put_line('End');
utl_file.Fclose(l_output);
END getfile;
The exact error is:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "USER.GETFILE", line 40
ORA-06512: at line 8
The error comes from utl_file.put_raw.
The maximum size of the buffer parameter is 32767 bytes.
You check for IF len < 32760 THEN, however, I see no guarantee in your code, that the len variable actually holds the length of the vblob variable that is the buffer in the put_raw call.
So I suppose the vblob variable's actual length is longer then 32767 and that is the reason for the error.
Hence I suggest to delete this piece of code:
IF len < 32760 THEN
-- dbms_output.put_line('Single write ');
utl_file.Put_raw(l_output, vblob);
utl_file.Fflush(l_output);
ELSE
also the END IF; of course, and always go for the 'write in pieces' branch.
I see now, you've done this based on the Burleson example which is good http://www.dba-oracle.com/t_writing_blob_clob_os_file.htm
but you see, unlike you, Burleson gets the len variable and the vblob variable from the same table and the same field.
-- get length of blob
SELECT dbms_lob.getlength(productblob)
INTO len
FROM products
WHERE id = product_id;
-- save blob length
x := len;
-- select blob into variable
SELECT product_blob
INTO vblob
FROM products
WHERE id = product_id;
EDIT
So an other option would be to fix the select for getting length. This means you'll have to replace this select:
-- get length of blob
SELECT dbms_lob.Getlength(FILENAME)
INTO len
FROM GENERAL.GUBFILE
WHERE gubfile_name = pfname;
with this:
-- get length of blob
SELECT dbms_lob.Getlength(BLOBVALUE)
INTO len
FROM FILES
WHERE filename = pfname;

Do I really need the parameters in this stored procedure?

The following stored procedure is intended to grab all the BLOB values from an Oracle database and save them into a folder called OraFolder.
It compiles fine but I have 2 questions.
1, there are 2 parameters, pname and display_name. I must admit that I don't know what they are there for because I just googled the code which seems to fit into our need.
My question is do I really need the 2 params given that we are trying to extract ALL BLOB values into a folder?
2, If your answer is yes, I do need them, how I do I use them?
Finally, there is an Entry_Id, I just kept getting an error that it is not declared. I had to remove it. What is it used for?
Sorr, I am not an Oracle guy, just trying to figure out a wa to fix a problem that is dropped on my laps.
Thanks in advance
Here is the complete stored proc.
create or replace PROCEDURE blob2file(pfname VARCHAR2, display_name in varchar2) IS
vblob BLOB;
vstart NUMBER := 1;
bytelen NUMBER := 32000;
len NUMBER;
my_vr RAW(32000);
x NUMBER;
v_name varchar2(100);
lv_str_len NUMBER;
l_output utl_file.file_type;
BEGIN
-- define output directory
lv_str_len := length(pfname);
--v_name := display_name||upper(substr(pfname,lv_str_len-3,lv_str_len));
v_name := display_name;
l_output := utl_file.fopen('MY_FOLDER', v_name, 'w', 32760);
-- get length of blob
SELECT dbms_lob.getlength(blob_content)
INTO len
FROM portal.WWDOC_DOCUMENT$
WHERE FILENAME = pfname;
-- dbms_output.put_line('Length: '||len);
-- save blob length
x := len;
-- select blob into variable
SELECT blob_content
INTO vblob
FROM portal.WWDOC_DOCUMENT$
WHERE FILENAME = pfname;
-- if small enough for a single write
IF len < 32760 THEN
-- dbms_output.put_line('Single write ');
utl_file.put_raw(l_output,vblob);
utl_file.fflush(l_output);
ELSE -- write in pieces
-- dbms_output.put_line('multi write '||vstart);
vstart := 1;
WHILE vstart < len
LOOP
dbms_lob.read(vblob,bytelen,vstart,my_vr);
utl_file.put_raw(l_output,my_vr);
utl_file.fflush(l_output);
-- set the start position for the next cut
vstart := vstart + bytelen;
-- set the end position if less than 32000 bytes
x := x - bytelen;
IF x < 32000 THEN
bytelen := x;
END IF;
END LOOP;
END IF;
dbms_output.put_line('End');
utl_file.fclose(l_output);
EXCEPTION
when others then dbms_output.put_line('ERROR:'||entry_id);
END blob2file;
The pfname param defines which file to grab, so you don't need that since you want them all.
The display_name param defines the output directory, so if you want to hard code the directory, you could.
Since you want all files, you'll need to loop through all records in the table and output them one at a time:
CREATE OR REPLACE PROCEDURE blob2file
IS
l_output utl_file.file_type;
vstart NUMBER := 1;
bytelen NUMBER := 32000;
x NUMBER;
my_vr RAW(32000);
BEGIN
FOR recFiles IN (SELECT dbms_lob.getlength(BLOB_CONTENT) as len,
FILENAME,
BLOB_CONTENT
FROM PORTAL.WWDOC_DOCUMENT$)
LOOP
l_output := utl_file.fopen('MY_FOLDER', '/hard code the path here/', 'w', 32760);
IF recFiles.len < 32760 THEN
utl_file.put_raw(l_output, recFiles.BLOB_CONTENT);
utl_file.fflush(l_output);
ELSE -- write in pieces
vstart := 1;
WHILE vstart < refFiles.len
LOOP
dbms_lob.read(recFiles.BLOB_CONTENT, bytelen, vstart, my_vr);
utl_file.put_raw(l_output, my_vr);
utl_file.fflush(l_output);
-- set the start position for the next cut
vstart := vstart + bytelen;
-- set the end position if less than 32000 bytes
x := x - bytelen;
IF x < 32000 THEN
bytelen := x;
END IF;
END LOOP;
END IF;
utl_file.fclose(l_output);
dbms_output.put_line('End');
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('ERROR: ' || SQLERRM);
END blob2file;
Looks like vname is the name of the file which the blob is written to. And pname is the key of the blob in the table. So if you're dumping all blobs in the table then you don't need either of these, but you will need to come up with a unique filename for each blob.

PL/SQL: Procedure not working correctly in a package

I'm working on a package with some procedures in them and I'm running into a bit of trouble. When I try to test the procedure to convert gallons to liters or the other procedures, it just prints out what was declared in the unnamed block instead of converting the numbers. Any ideas?
CREATE OR REPLACE PACKAGE eng_metric
IS
PROCEDURE convert(degree_fahrenheit IN OUT NUMBER,degree_celsius IN OUT NUMBER,measure IN VARCHAR2);
PROCEDURE convert(liters IN OUT NUMBER,gallons IN OUT NUMBER);
END eng_metric;
/
CREATE OR REPLACE PACKAGE BODY eng_metric
AS
PROCEDURE Convert
(degree_fahrenheit IN OUT NUMBER,
degree_celsius IN OUT NUMBER,
measure IN VARCHAR2)
IS
df NUMBER;
dc NUMBER;
convertf NUMBER;
measurecf VARCHAR2(4);
BEGIN
measurecf := measure;
df := degree_fahrenheit;
dc := degree_celsius;
IF measure = 'TEMP' THEN
IF dc = NULL THEN
convertf := ((df - 32) * .56);
degree_fahrenheit := convertf;
dbms_output.Put_line('The temperature in fahrenheit is '
||To_char(degree_fahrenheit));
ELSIF df = NULL THEN
convertf := (dc + 17.98) * 1.8;
degree_celsius := convertf;
END IF;
ELSE
dbms_output.Put_line('Invalid measure');
END IF;
END convert;
PROCEDURE Convert
(liters IN OUT NUMBER,
gallons IN OUT NUMBER)
IS
lit NUMBER;
gal NUMBER;
convertlg NUMBER;
BEGIN
lit := liters;
gal := gallons;
IF gal = NULL THEN
convertlg := (lit / 3.785);
liters := convertlg;
ELSIF lit = NULL THEN
convertlg := (gal * 3.785);
gallons := convertlg;
END IF;
END convert;
END eng_metric;
/
DECLARE
liters NUMBER := 25;
gallons NUMBER := 41;
nully NUMBER := NULL;
BEGIN
eng_metric.Convert(nully,gallons);
dbms_output.Put_line(To_char(gallons));
END;
/
Instead of
IF gal = NULL THEN
you need
IF gal IS NULL
What you have to remember is that NULL means "no value". It NEVER equals or fails to equal anything, including NULL. So you need to use IS NULL or IS NOT NULL, or use the NVL function to change the null to something that has a value.

Resources