Repository: deepset-ai/haystack
Pull request: Extends input types of `RemoteWhisperTranscriber`
### Related Issues

- fixes https://github.com/deepset-ai/haystack/issues/6216

### Proposed Changes:
Extends input types of `RemoteWhisperTranscriber` from `List[ByteStream]` to `List[Union[str, Path, ByteStream]]` to make possible to connect it to `FileTypeRouter` (see issue)

### How did you test it?

Local tests

### Notes for the reviewer

<!-- E.g. point out section where the reviewer  -->

### Checklist

- I have read the [contributors guidelines](https://github.com/deepset-ai/haystack/blob/main/CONTRIBUTING.md) and the [code of conduct](https://github.com/deepset-ai/haystack/blob/main/code_of_conduct.txt)
- I have updated the related issue with new insights and changes
- I added unit tests and updated the docstrings
- I've used one of the [conventional commit types](https://www.conventionalcommits.org/en/v1.0.0/) for my PR title: `fix:`, `feat:`, `build:`, `chore:`, `ci:`, `docs:`, `style:`, `refactor:`, `perf:`, `test:`.
- I documented my code
- I ran [pre-commit hooks](https://github.com/deepset-ai/haystack/blob/main/CONTRIBUTING.md#installation) and fixed any issue


Problem statement:
Cannot connect `FileTypeRouter` to `RemoteWhisperTranscriber`
Error raised:

```
Cannot connect 'router.audio/mpeg' with 'mp3_converter.streams': their declared input and output types do not match.
'router':
 - audio/mpeg (List[Path])
'mp3_converter':
 - streams (List[ByteStream]), available
```

We should make `RemoteWhisperTranscriber` take `List[Union[str, Path, ByteStream]]` to be able to connect to this router.

Extending the output type of `FileTypeRouter` only won't fix this issue and might break other converters. We can make a `ByteStreamTypeRouter` later to be able to route stream (we will need it for Web RAG).

Hints from issue discussion:

Commit message: feedback

Patch to review:
diff --git a/haystack/preview/components/audio/whisper_remote.py b/haystack/preview/components/audio/whisper_remote.py
index 7228b014ca..15d17e19a4 100644
--- a/haystack/preview/components/audio/whisper_remote.py
+++ b/haystack/preview/components/audio/whisper_remote.py
@@ -1,7 +1,8 @@
 import io
 import logging
 import os
-from typing import Any, Dict, List, Optional
+from typing import Any, Dict, List, Optional, Union
+from pathlib import Path
 
 import openai
 
@@ -111,7 +112,7 @@ def from_dict(cls, data: Dict[str, Any]) -> "RemoteWhisperTranscriber":
         return default_from_dict(cls, data)
 
     @component.output_types(documents=List[Document])
-    def run(self, streams: List[ByteStream]):
+    def run(self, sources: List[Union[str, Path, ByteStream]]):
         """
         Transcribe the audio files into a list of Documents, one for each input file.
 
@@ -124,11 +125,21 @@ def run(self, streams: List[ByteStream]):
         """
         documents = []
 
-        for stream in streams:
-            file = io.BytesIO(stream.data)
-            file.name = stream.metadata.get("file_path", "audio_input.wav")  # default name if `file_path` not found
+        for source in sources:
+            if isinstance(source, Path):
+                source = str(source.absolute())
+
+            if isinstance(source, str):
+                path = source
+                source = ByteStream.from_file_path(source)
+                source.metadata["file_path"] = path
+
+            file = io.BytesIO(source.data)
+            if "file_path" in source.metadata:
+                file.name = source.metadata["file_path"]
+
             content = openai.Audio.transcribe(file=file, model=self.model_name, **self.whisper_params)
-            doc = Document(content=content["text"], meta=stream.metadata)
+            doc = Document(content=content["text"], meta=source.metadata)
             documents.append(doc)
 
         return {"documents": documents}
diff --git a/releasenotes/notes/remotetranscriber-input-type-aae9a255435a3507.yaml b/releasenotes/notes/remotetranscriber-input-type-aae9a255435a3507.yaml
new file mode 100644
index 0000000000..3ca8b5bcd1
--- /dev/null
+++ b/releasenotes/notes/remotetranscriber-input-type-aae9a255435a3507.yaml
@@ -0,0 +1,2 @@
+preview:
+  - Extends input types of RemoteWhisperTranscriber from List[ByteStream] to List[Union[str, Path, ByteStream]] to make possible to connect it to FileTypeRouter.
diff --git a/test/preview/components/audio/test_whisper_remote.py b/test/preview/components/audio/test_whisper_remote.py
index 5321386f99..f198edb678 100644
--- a/test/preview/components/audio/test_whisper_remote.py
+++ b/test/preview/components/audio/test_whisper_remote.py
@@ -1,5 +1,6 @@
 import os
 from unittest.mock import patch
+from pathlib import Path
 
 import openai
 import pytest
@@ -182,7 +183,33 @@ def test_from_dict_with_defualt_parameters_no_env_var(self, monkeypatch):
             RemoteWhisperTranscriber.from_dict(data)
 
     @pytest.mark.unit
-    def test_run(self, preview_samples_path):
+    def test_run_str(self, preview_samples_path):
+        with patch("haystack.preview.components.audio.whisper_remote.openai.Audio") as openai_audio_patch:
+            model = "whisper-1"
+            file_path = str(preview_samples_path / "audio" / "this is the content of the document.wav")
+            openai_audio_patch.transcribe.side_effect = mock_openai_response
+
+            transcriber = RemoteWhisperTranscriber(api_key="test_api_key", model_name=model, response_format="json")
+            result = transcriber.run(sources=[file_path])
+
+            assert result["documents"][0].content == "test transcription"
+            assert result["documents"][0].meta["file_path"] == file_path
+
+    @pytest.mark.unit
+    def test_run_path(self, preview_samples_path):
+        with patch("haystack.preview.components.audio.whisper_remote.openai.Audio") as openai_audio_patch:
+            model = "whisper-1"
+            file_path = preview_samples_path / "audio" / "this is the content of the document.wav"
+            openai_audio_patch.transcribe.side_effect = mock_openai_response
+
+            transcriber = RemoteWhisperTranscriber(api_key="test_api_key", model_name=model, response_format="json")
+            result = transcriber.run(sources=[file_path])
+
+            assert result["documents"][0].content == "test transcription"
+            assert result["documents"][0].meta["file_path"] == str(file_path.absolute())
+
+    @pytest.mark.unit
+    def test_run_bytestream(self, preview_samples_path):
         with patch("haystack.preview.components.audio.whisper_remote.openai.Audio") as openai_audio_patch:
             model = "whisper-1"
             file_path = preview_samples_path / "audio" / "this is the content of the document.wav"
@@ -193,7 +220,7 @@ def test_run(self, preview_samples_path):
                 byte_stream = audio_stream.read()
                 audio_file = ByteStream(byte_stream, metadata={"file_path": str(file_path.absolute())})
 
-                result = transcriber.run(streams=[audio_file])
+                result = transcriber.run(sources=[audio_file])
 
                 assert result["documents"][0].content == "test transcription"
                 assert result["documents"][0].meta["file_path"] == str(file_path.absolute())
@@ -208,18 +235,11 @@ def test_whisper_remote_transcriber(self, preview_samples_path):
 
         paths = [
             preview_samples_path / "audio" / "this is the content of the document.wav",
-            preview_samples_path / "audio" / "the context for this answer is here.wav",
-            preview_samples_path / "audio" / "answer.wav",
+            str(preview_samples_path / "audio" / "the context for this answer is here.wav"),
+            ByteStream.from_file_path(preview_samples_path / "audio" / "answer.wav"),
         ]
 
-        audio_files = []
-        for file_path in paths:
-            with open(file_path, "rb") as audio_stream:
-                byte_stream = audio_stream.read()
-                audio_file = ByteStream(byte_stream, metadata={"file_path": str(file_path.absolute())})
-                audio_files.append(audio_file)
-
-        output = transcriber.run(streams=audio_files)
+        output = transcriber.run(sources=paths)
 
         docs = output["documents"]
         assert len(docs) == 3
@@ -236,4 +256,3 @@ def test_whisper_remote_transcriber(self, preview_samples_path):
         )
 
         assert docs[2].content.strip().lower() == "answer."
-        assert str((preview_samples_path / "audio" / "answer.wav").absolute()) == docs[2].meta["file_path"]


Code review comments:
diff_hunk: @@ -124,11 +125,21 @@ def run(self, streams: List[ByteStream]):
         """
         documents = []
 
-        for stream in streams:
-            file = io.BytesIO(stream.data)
-            file.name = stream.metadata.get("file_path", "audio_input.wav")  # default name if `file_path` not found
+        for source in sources:
+            if isinstance(source, Path):
+                source = str(source.absolute())
+
+            if isinstance(source, str):
+                path = source
+                source = ByteStream.from_file_path(source)
+                source.metadata["file_path"] = path
path: haystack/preview/components/audio/whisper_remote.py
text: @user1:
I've seen this code multiple times now, the storing of file_path into metadata. 
```
source.metadata["file_path"] = path
```

Let's do that automatically in ByteStream.from_file_path?

@author:
Yes, but let's do that in another PR :+1: