Repository: yt-dlp/yt-dlp

Issue:
[ie/joqrag] Add extractor
**IMPORTANT**: PRs without the template will be CLOSED

### Description of your *pull request* and other information

<!--

Explanation of your *pull request* in arbitrary form goes here. Please **make sure the description explains the purpose and effect** of your *pull request* and is worded well enough to be understood. Provide as much **context and examples** as possible

-->

Hi! I would like to let yt-dlp support radio "[超!A&G+](https://ja.wikipedia.org/wiki/%E8%B6%85!A%26G%2B)" on [Nippon Cultural Broadcasting (JOQR)](https://en.wikipedia.org/wiki/Nippon_Cultural_Broadcasting).

<img src="https://www.joqr.co.jp/wp/wp-content/themes/joqr/img/ag_noimage_artcl.png" width="251" title="A&G+ logo" />

Please do not to be confused with Radiko (radiko.py).

<details open><summary>Template</summary> <!-- OPEN is intentional -->

<!--

# PLEASE FOLLOW THE GUIDE BELOW

- You will be asked some questions, please read them **carefully** and answer honestly
- Put an `x` into all the boxes `[ ]` relevant to your *pull request* (like [x])
- Use *Preview* tab to see how your *pull request* will actually look like

-->

### Before submitting a *pull request* make sure you have:
- [x] At least skimmed through [contributing guidelines](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#developer-instructions) including [yt-dlp coding conventions](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#yt-dlp-coding-conventions)
- [x] [Searched](https://github.com/yt-dlp/yt-dlp/search?q=is%3Apr&type=Issues) the bugtracker for similar pull requests
- [x] Checked the code with [flake8](https://pypi.python.org/pypi/flake8) and [ran relevant tests](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#developer-instructions)

### In order to be accepted and merged into yt-dlp each piece of code must be in public domain or released under [Unlicense](http://unlicense.org/). Check all of the following options that apply:
- [x] I am the original author of this code and I am willing to release it under [Unlicense](http://unlicense.org/)
- [ ] I am not the original author of this code but it is in public domain or released under [Unlicense](http://unlicense.org/) (provide reliable evidence)

### What is the purpose of your *pull request*?
- [ ] Fix or improvement to an extractor (Make sure to add/update tests)
- [x] New extractor ([Piracy websites will not be accepted](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-website-primarily-used-for-piracy))
- [ ] Core bug fix/improvement
- [ ] New feature (It is strongly [recommended to open an issue first](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#adding-new-feature-or-making-overarching-changes))


<!-- Do NOT edit/remove anything below this! -->
</details><details><summary>Copilot Summary</summary>  

<!--
copilot:all
-->
### <samp>🤖 Generated by Copilot at 4f478a9</samp>

### Summary
📻🇯🇵🎙️

<!--
1.  📻 - This emoji represents radio, which is the main medium of JOQR. It also conveys the idea of streaming audio content, which is what the extractor enables.
2.  🇯🇵 - This emoji represents Japan, which is the country where JOQR is based and broadcasts. It also indicates the language and culture of the radio station and its programs, which may appeal to some users.
3.  🎙️ - This emoji represents microphone, which is a symbol of voice actors and radio hosts, who are the main personalities and guests of JOQR. It also suggests the quality and variety of the audio content, which may attract some users.
-->
Add support for extracting live streams from JOQR radio station. Add `joqrag.py` module with `JoqrAgIE` class and import it in `_extractors.py`.

> _`JoqrAgIE` class_
> _Extracts anime radio streams_
> _Autumn leaves flutter_

### Walkthrough
* Implement live stream extraction for JOQR radio station ([link](https://github.com/yt-dlp/yt-dlp/pull/8384/files?diff=unified&w=0#diff-780b22dc7eb280f5a7b2bbf79aff17826de88ddcbf2fc1116ba19901827aa4e3R898), [link](https://github.com/yt-dlp/yt-dlp/pull/8384/files?diff=unified&w=0#diff-671d4527db88fd740fb7c65caeece065d6b6dfd56bdf2f3f0d95e80f25d178f3R1-R54))
  - Import `JoqrAgIE` class from `joqrag.py` module in `_extractors.py` ([link](https://github.com/yt-dlp/yt-dlp/pull/8384/files?diff=unified&w=0#diff-780b22dc7eb280f5a7b2bbf79aff17826de88ddcbf2fc1116ba19901827aa4e3R898))
  - Define `JoqrAgIE` class in `joqrag.py` module that inherits from `InfoExtractor` base class ([link](https://github.com/yt-dlp/yt-dlp/pull/8384/files?diff=unified&w=0#diff-671d4527db88fd740fb7c65caeece065d6b6dfd56bdf2f3f0d95e80f25d178f3R1-R54))
  - Override `suitable` and `ie_key` methods to match the JOQR website url and return the extractor key ([link](https://github.com/yt-dlp/yt-dlp/pull/8384/files?diff=unified&w=0#diff-671d4527db88fd740fb7c65caeece065d6b6dfd56bdf2f3f0d95e80f25d178f3R1-R54))
  - Override `_real_extract` method to parse the webpage and extract the metadata and the m3u8 url using `self._html_search_regex` and `self._extract_m3u8_formats` methods ([link](https://github.com/yt-dlp/yt-dlp/pull/8384/files?diff=unified&w=0#diff-671d4527db88fd740fb7c65caeece065d6b6dfd56bdf2f3f0d95e80f25d178f3R1-R54))
  - Return a dictionary with the extracted information, such as `id`, `title`, `formats`, `is_live`, etc. ([link](https://github.com/yt-dlp/yt-dlp/pull/8384/files?diff=unified&w=0#diff-671d4527db88fd740fb7c65caeece065d6b6dfd56bdf2f3f0d95e80f25d178f3R1-R54))
  - Add a test case for the extractor in `joqrag.py` module that checks the `id`, `title`, `ext`, and `is_live` fields of the extracted information ([link](https://github.com/yt-dlp/yt-dlp/pull/8384/files?diff=unified&w=0#diff-671d4527db88fd740fb7c65caeece065d6b6dfd56bdf2f3f0d95e80f25d178f3R1-R54))



</details>



Discussion:
Hi there, I'm going to add a change:

The "joqrag" site stops at night and plays a loop video containing their logo. I think that the extractor should not download the stream but treat it as an "up_coming" live and "`raise_no_formats()`".

Now convert this PR to draft.

Resolving patch:
diff --git a/yt_dlp/extractor/joqrag.py b/yt_dlp/extractor/joqrag.py
--- a/yt_dlp/extractor/joqrag.py
+++ b/yt_dlp/extractor/joqrag.py
@@ -80,7 +80,7 @@ def _real_extract(self, url):
             note='Downloading metadata', errnote='Failed to download metadata')
         title = self._extract_metadata('Program_name', metadata)
 
-        if title == '放送休止':
+        if not title or title == '放送休止':
             formats = []
             live_status = 'is_upcoming'
             release_timestamp = self._extract_start_timestamp(video_id, False)


Test patch: