Upload 131 files
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .github/ISSUE_TEMPLATE/bug_report.yml +70 -0
- .github/ISSUE_TEMPLATE/config.yml +7 -0
- .github/workflows/update-build-number.yml +37 -0
- LICENSE +340 -0
- docker-compose.md +200 -0
- docs/adding_routes.md +81 -0
- docs/audio/concatenate.md +207 -0
- docs/cloud-installation/do.md +109 -0
- docs/cloud-installation/gcp.md +140 -0
- docs/code/execute/execute_python.md +174 -0
- docs/ffmpeg/ffmpeg_compose.md +245 -0
- docs/image/convert/image_to_video.md +159 -0
- docs/image/screenshot_webpage.md +218 -0
- docs/media/convert/media_convert.md +138 -0
- docs/media/convert/media_to_mp3.md +142 -0
- docs/media/cut.md +169 -0
- docs/media/download.md +317 -0
- docs/media/feedback.md +44 -0
- docs/media/generate_ass.md +350 -0
- docs/media/media_transcribe.md +270 -0
- docs/media/metadata.md +156 -0
- docs/media/silence.md +176 -0
- docs/s3/upload.md +66 -0
- docs/toolkit/authenticate.md +92 -0
- docs/toolkit/job_status.md +141 -0
- docs/toolkit/jobs_status.md +141 -0
- docs/toolkit/test.md +89 -0
- docs/video/caption_video.md +354 -0
- docs/video/concatenate.md +180 -0
- docs/video/cut.md +197 -0
- docs/video/split.md +173 -0
- docs/video/thumbnail.md +165 -0
- docs/video/trim.md +147 -0
- routes/audio_mixing.py +73 -0
- routes/authenticate.py +35 -0
- routes/caption_video.py +92 -0
- routes/combine_videos.py +70 -0
- routes/extract_keyframes.py +66 -0
- routes/gdrive_upload.py +265 -0
- routes/image_to_video.py +72 -0
- routes/media_to_mp3.py +64 -0
- routes/transcribe_media.py +68 -0
- routes/v1/audio/concatenate.py +57 -0
- routes/v1/code/execute/execute_python.py +140 -0
- routes/v1/ffmpeg/ffmpeg_compose.py +149 -0
- routes/v1/image/convert/image_to_video.py +73 -0
- routes/v1/image/screenshot_webpage.py +125 -0
- routes/v1/media/convert/media_convert.py +81 -0
- routes/v1/media/convert/media_to_mp3.py +67 -0
- routes/v1/media/download.py +230 -0
.github/ISSUE_TEMPLATE/bug_report.yml
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Bug Report
|
| 2 |
+
title: "[Bug]: "
|
| 3 |
+
labels: ["bug"]
|
| 4 |
+
description: Report broken or incorrect behaviour
|
| 5 |
+
body:
|
| 6 |
+
- type: markdown
|
| 7 |
+
attributes:
|
| 8 |
+
value: >
|
| 9 |
+
Thanks for taking the time to fill out a bug.
|
| 10 |
+
Please note that this form is for bugs only!
|
| 11 |
+
- type: textarea
|
| 12 |
+
id: what-happened
|
| 13 |
+
attributes:
|
| 14 |
+
label: Describe the bug
|
| 15 |
+
description: A clear and concise description of what the bug is.
|
| 16 |
+
validations:
|
| 17 |
+
required: true
|
| 18 |
+
- type: textarea
|
| 19 |
+
attributes:
|
| 20 |
+
label: Reproduction Steps
|
| 21 |
+
description: >
|
| 22 |
+
What you did to make it happen. Include any request payloads, API calls, or specific commands used.
|
| 23 |
+
validations:
|
| 24 |
+
required: true
|
| 25 |
+
- type: textarea
|
| 26 |
+
attributes:
|
| 27 |
+
label: Expected behavior
|
| 28 |
+
description: >
|
| 29 |
+
A clear and concise description of what you expected to happen.
|
| 30 |
+
validations:
|
| 31 |
+
required: false
|
| 32 |
+
- type: textarea
|
| 33 |
+
attributes:
|
| 34 |
+
label: Screenshots and relevant files
|
| 35 |
+
description: >
|
| 36 |
+
If applicable, add screenshots or relevant files to help explain your problem.
|
| 37 |
+
validations:
|
| 38 |
+
required: false
|
| 39 |
+
- type: dropdown
|
| 40 |
+
id: platform
|
| 41 |
+
attributes:
|
| 42 |
+
label: "Platform"
|
| 43 |
+
description: "Select the platform where you encountered this bug"
|
| 44 |
+
options:
|
| 45 |
+
- "Google Cloud Platform"
|
| 46 |
+
- "Digital Ocean"
|
| 47 |
+
- "Local"
|
| 48 |
+
- "Other"
|
| 49 |
+
validations:
|
| 50 |
+
required: true
|
| 51 |
+
- type: dropdown
|
| 52 |
+
id: assign
|
| 53 |
+
attributes:
|
| 54 |
+
label: "Would you like to work on this issue?"
|
| 55 |
+
options:
|
| 56 |
+
- "Yes"
|
| 57 |
+
- type: checkboxes
|
| 58 |
+
attributes:
|
| 59 |
+
label: Checklist
|
| 60 |
+
description: >
|
| 61 |
+
Let's make sure you've properly done due diligence when reporting this issue!
|
| 62 |
+
options:
|
| 63 |
+
- label: I have searched the open issues for duplicates.
|
| 64 |
+
required: true
|
| 65 |
+
- label: I have shown the entire traceback, if possible.
|
| 66 |
+
required: true
|
| 67 |
+
- type: textarea
|
| 68 |
+
attributes:
|
| 69 |
+
label: Additional Context
|
| 70 |
+
description: Add any other context about the problem here.
|
.github/ISSUE_TEMPLATE/config.yml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
contact_links:
|
| 2 |
+
- name: Help or Questions?
|
| 3 |
+
url: https://www.skool.com/no-code-architects/about?ref=2f302c52a77541efa2dd5e8b27f3f8c9
|
| 4 |
+
about: Get courses, community, support daily calls and more.
|
| 5 |
+
|
| 6 |
+
blank_issues_enabled: true
|
| 7 |
+
# Allow blank issues for now?
|
.github/workflows/update-build-number.yml
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Update Build Number
|
| 2 |
+
on:
|
| 3 |
+
push:
|
| 4 |
+
branches:
|
| 5 |
+
- build
|
| 6 |
+
permissions:
|
| 7 |
+
contents: write
|
| 8 |
+
jobs:
|
| 9 |
+
update-build-number:
|
| 10 |
+
runs-on: ubuntu-latest
|
| 11 |
+
steps:
|
| 12 |
+
- name: Checkout repository
|
| 13 |
+
uses: actions/checkout@v4
|
| 14 |
+
with:
|
| 15 |
+
fetch-depth: 0
|
| 16 |
+
token: ${{ secrets.ACTION_BYPASS }}
|
| 17 |
+
|
| 18 |
+
- name: Update build number
|
| 19 |
+
run: |
|
| 20 |
+
if [ ! -f build_number.txt ]; then
|
| 21 |
+
echo "0" > build_number.txt
|
| 22 |
+
fi
|
| 23 |
+
build_number=$(( $(cat build_number.txt) + 1 ))
|
| 24 |
+
echo $build_number > build_number.txt
|
| 25 |
+
|
| 26 |
+
# Update the version in your code file (e.g., version.py)
|
| 27 |
+
echo "BUILD_NUMBER = $build_number" > version.py
|
| 28 |
+
|
| 29 |
+
git config user.name github-actions
|
| 30 |
+
git config user.email github-actions@github.com
|
| 31 |
+
git add build_number.txt version.py
|
| 32 |
+
git commit -m "Build $build_number: Bump build number [skip ci]"
|
| 33 |
+
git push origin build
|
| 34 |
+
|
| 35 |
+
- name: Push changes to testing branch
|
| 36 |
+
run: |
|
| 37 |
+
git push origin build:testing --force
|
LICENSE
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Copyright (c) 2025 Stephen G. Pope
|
| 2 |
+
|
| 3 |
+
GNU GENERAL PUBLIC LICENSE
|
| 4 |
+
Version 2, June 1991
|
| 5 |
+
|
| 6 |
+
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
| 7 |
+
<https://fsf.org/>
|
| 8 |
+
Everyone is permitted to copy and distribute verbatim copies
|
| 9 |
+
of this license document, but changing it is not allowed.
|
| 10 |
+
|
| 11 |
+
Preamble
|
| 12 |
+
|
| 13 |
+
The licenses for most software are designed to take away your
|
| 14 |
+
freedom to share and change it. By contrast, the GNU General Public
|
| 15 |
+
License is intended to guarantee your freedom to share and change free
|
| 16 |
+
software--to make sure the software is free for all its users. This
|
| 17 |
+
General Public License applies to most of the Free Software
|
| 18 |
+
Foundation's software and to any other program whose authors commit to
|
| 19 |
+
using it. (Some other Free Software Foundation software is covered by
|
| 20 |
+
the GNU Lesser General Public License instead.) You can apply it to
|
| 21 |
+
your programs, too.
|
| 22 |
+
|
| 23 |
+
When we speak of free software, we are referring to freedom, not
|
| 24 |
+
price. Our General Public Licenses are designed to make sure that you
|
| 25 |
+
have the freedom to distribute copies of free software (and charge for
|
| 26 |
+
this service if you wish), that you receive source code or can get it
|
| 27 |
+
if you want it, that you can change the software or use pieces of it
|
| 28 |
+
in new free programs; and that you know you can do these things.
|
| 29 |
+
|
| 30 |
+
To protect your rights, we need to make restrictions that forbid
|
| 31 |
+
anyone to deny you these rights or to ask you to surrender the rights.
|
| 32 |
+
These restrictions translate to certain responsibilities for you if you
|
| 33 |
+
distribute copies of the software, or if you modify it.
|
| 34 |
+
|
| 35 |
+
For example, if you distribute copies of such a program, whether
|
| 36 |
+
gratis or for a fee, you must give the recipients all the rights that
|
| 37 |
+
you have. You must make sure that they, too, receive or can get the
|
| 38 |
+
source code. And you must show them these terms so they know their
|
| 39 |
+
rights.
|
| 40 |
+
|
| 41 |
+
We protect your rights with two steps: (1) copyright the software, and
|
| 42 |
+
(2) offer you this license which gives you legal permission to copy,
|
| 43 |
+
distribute and/or modify the software.
|
| 44 |
+
|
| 45 |
+
Also, for each author's protection and ours, we want to make certain
|
| 46 |
+
that everyone understands that there is no warranty for this free
|
| 47 |
+
software. If the software is modified by someone else and passed on, we
|
| 48 |
+
want its recipients to know that what they have is not the original, so
|
| 49 |
+
that any problems introduced by others will not reflect on the original
|
| 50 |
+
authors' reputations.
|
| 51 |
+
|
| 52 |
+
Finally, any free program is threatened constantly by software
|
| 53 |
+
patents. We wish to avoid the danger that redistributors of a free
|
| 54 |
+
program will individually obtain patent licenses, in effect making the
|
| 55 |
+
program proprietary. To prevent this, we have made it clear that any
|
| 56 |
+
patent must be licensed for everyone's free use or not licensed at all.
|
| 57 |
+
|
| 58 |
+
The precise terms and conditions for copying, distribution and
|
| 59 |
+
modification follow.
|
| 60 |
+
|
| 61 |
+
GNU GENERAL PUBLIC LICENSE
|
| 62 |
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
| 63 |
+
|
| 64 |
+
0. This License applies to any program or other work which contains
|
| 65 |
+
a notice placed by the copyright holder saying it may be distributed
|
| 66 |
+
under the terms of this General Public License. The "Program", below,
|
| 67 |
+
refers to any such program or work, and a "work based on the Program"
|
| 68 |
+
means either the Program or any derivative work under copyright law:
|
| 69 |
+
that is to say, a work containing the Program or a portion of it,
|
| 70 |
+
either verbatim or with modifications and/or translated into another
|
| 71 |
+
language. (Hereinafter, translation is included without limitation in
|
| 72 |
+
the term "modification".) Each licensee is addressed as "you".
|
| 73 |
+
|
| 74 |
+
Activities other than copying, distribution and modification are not
|
| 75 |
+
covered by this License; they are outside its scope. The act of
|
| 76 |
+
running the Program is not restricted, and the output from the Program
|
| 77 |
+
is covered only if its contents constitute a work based on the
|
| 78 |
+
Program (independent of having been made by running the Program).
|
| 79 |
+
Whether that is true depends on what the Program does.
|
| 80 |
+
|
| 81 |
+
1. You may copy and distribute verbatim copies of the Program's
|
| 82 |
+
source code as you receive it, in any medium, provided that you
|
| 83 |
+
conspicuously and appropriately publish on each copy an appropriate
|
| 84 |
+
copyright notice and disclaimer of warranty; keep intact all the
|
| 85 |
+
notices that refer to this License and to the absence of any warranty;
|
| 86 |
+
and give any other recipients of the Program a copy of this License
|
| 87 |
+
along with the Program.
|
| 88 |
+
|
| 89 |
+
You may charge a fee for the physical act of transferring a copy, and
|
| 90 |
+
you may at your option offer warranty protection in exchange for a fee.
|
| 91 |
+
|
| 92 |
+
2. You may modify your copy or copies of the Program or any portion
|
| 93 |
+
of it, thus forming a work based on the Program, and copy and
|
| 94 |
+
distribute such modifications or work under the terms of Section 1
|
| 95 |
+
above, provided that you also meet all of these conditions:
|
| 96 |
+
|
| 97 |
+
a) You must cause the modified files to carry prominent notices
|
| 98 |
+
stating that you changed the files and the date of any change.
|
| 99 |
+
|
| 100 |
+
b) You must cause any work that you distribute or publish, that in
|
| 101 |
+
whole or in part contains or is derived from the Program or any
|
| 102 |
+
part thereof, to be licensed as a whole at no charge to all third
|
| 103 |
+
parties under the terms of this License.
|
| 104 |
+
|
| 105 |
+
c) If the modified program normally reads commands interactively
|
| 106 |
+
when run, you must cause it, when started running for such
|
| 107 |
+
interactive use in the most ordinary way, to print or display an
|
| 108 |
+
announcement including an appropriate copyright notice and a
|
| 109 |
+
notice that there is no warranty (or else, saying that you provide
|
| 110 |
+
a warranty) and that users may redistribute the program under
|
| 111 |
+
these conditions, and telling the user how to view a copy of this
|
| 112 |
+
License. (Exception: if the Program itself is interactive but
|
| 113 |
+
does not normally print such an announcement, your work based on
|
| 114 |
+
the Program is not required to print an announcement.)
|
| 115 |
+
|
| 116 |
+
These requirements apply to the modified work as a whole. If
|
| 117 |
+
identifiable sections of that work are not derived from the Program,
|
| 118 |
+
and can be reasonably considered independent and separate works in
|
| 119 |
+
themselves, then this License, and its terms, do not apply to those
|
| 120 |
+
sections when you distribute them as separate works. But when you
|
| 121 |
+
distribute the same sections as part of a whole which is a work based
|
| 122 |
+
on the Program, the distribution of the whole must be on the terms of
|
| 123 |
+
this License, whose permissions for other licensees extend to the
|
| 124 |
+
entire whole, and thus to each and every part regardless of who wrote it.
|
| 125 |
+
|
| 126 |
+
Thus, it is not the intent of this section to claim rights or contest
|
| 127 |
+
your rights to work written entirely by you; rather, the intent is to
|
| 128 |
+
exercise the right to control the distribution of derivative or
|
| 129 |
+
collective works based on the Program.
|
| 130 |
+
|
| 131 |
+
In addition, mere aggregation of another work not based on the Program
|
| 132 |
+
with the Program (or with a work based on the Program) on a volume of
|
| 133 |
+
a storage or distribution medium does not bring the other work under
|
| 134 |
+
the scope of this License.
|
| 135 |
+
|
| 136 |
+
3. You may copy and distribute the Program (or a work based on it,
|
| 137 |
+
under Section 2) in object code or executable form under the terms of
|
| 138 |
+
Sections 1 and 2 above provided that you also do one of the following:
|
| 139 |
+
|
| 140 |
+
a) Accompany it with the complete corresponding machine-readable
|
| 141 |
+
source code, which must be distributed under the terms of Sections
|
| 142 |
+
1 and 2 above on a medium customarily used for software interchange; or,
|
| 143 |
+
|
| 144 |
+
b) Accompany it with a written offer, valid for at least three
|
| 145 |
+
years, to give any third party, for a charge no more than your
|
| 146 |
+
cost of physically performing source distribution, a complete
|
| 147 |
+
machine-readable copy of the corresponding source code, to be
|
| 148 |
+
distributed under the terms of Sections 1 and 2 above on a medium
|
| 149 |
+
customarily used for software interchange; or,
|
| 150 |
+
|
| 151 |
+
c) Accompany it with the information you received as to the offer
|
| 152 |
+
to distribute corresponding source code. (This alternative is
|
| 153 |
+
allowed only for noncommercial distribution and only if you
|
| 154 |
+
received the program in object code or executable form with such
|
| 155 |
+
an offer, in accord with Subsection b above.)
|
| 156 |
+
|
| 157 |
+
The source code for a work means the preferred form of the work for
|
| 158 |
+
making modifications to it. For an executable work, complete source
|
| 159 |
+
code means all the source code for all modules it contains, plus any
|
| 160 |
+
associated interface definition files, plus the scripts used to
|
| 161 |
+
control compilation and installation of the executable. However, as a
|
| 162 |
+
special exception, the source code distributed need not include
|
| 163 |
+
anything that is normally distributed (in either source or binary
|
| 164 |
+
form) with the major components (compiler, kernel, and so on) of the
|
| 165 |
+
operating system on which the executable runs, unless that component
|
| 166 |
+
itself accompanies the executable.
|
| 167 |
+
|
| 168 |
+
If distribution of executable or object code is made by offering
|
| 169 |
+
access to copy from a designated place, then offering equivalent
|
| 170 |
+
access to copy the source code from the same place counts as
|
| 171 |
+
distribution of the source code, even though third parties are not
|
| 172 |
+
compelled to copy the source along with the object code.
|
| 173 |
+
|
| 174 |
+
4. You may not copy, modify, sublicense, or distribute the Program
|
| 175 |
+
except as expressly provided under this License. Any attempt
|
| 176 |
+
otherwise to copy, modify, sublicense or distribute the Program is
|
| 177 |
+
void, and will automatically terminate your rights under this License.
|
| 178 |
+
However, parties who have received copies, or rights, from you under
|
| 179 |
+
this License will not have their licenses terminated so long as such
|
| 180 |
+
parties remain in full compliance.
|
| 181 |
+
|
| 182 |
+
5. You are not required to accept this License, since you have not
|
| 183 |
+
signed it. However, nothing else grants you permission to modify or
|
| 184 |
+
distribute the Program or its derivative works. These actions are
|
| 185 |
+
prohibited by law if you do not accept this License. Therefore, by
|
| 186 |
+
modifying or distributing the Program (or any work based on the
|
| 187 |
+
Program), you indicate your acceptance of this License to do so, and
|
| 188 |
+
all its terms and conditions for copying, distributing or modifying
|
| 189 |
+
the Program or works based on it.
|
| 190 |
+
|
| 191 |
+
6. Each time you redistribute the Program (or any work based on the
|
| 192 |
+
Program), the recipient automatically receives a license from the
|
| 193 |
+
original licensor to copy, distribute or modify the Program subject to
|
| 194 |
+
these terms and conditions. You may not impose any further
|
| 195 |
+
restrictions on the recipients' exercise of the rights granted herein.
|
| 196 |
+
You are not responsible for enforcing compliance by third parties to
|
| 197 |
+
this License.
|
| 198 |
+
|
| 199 |
+
7. If, as a consequence of a court judgment or allegation of patent
|
| 200 |
+
infringement or for any other reason (not limited to patent issues),
|
| 201 |
+
conditions are imposed on you (whether by court order, agreement or
|
| 202 |
+
otherwise) that contradict the conditions of this License, they do not
|
| 203 |
+
excuse you from the conditions of this License. If you cannot
|
| 204 |
+
distribute so as to satisfy simultaneously your obligations under this
|
| 205 |
+
License and any other pertinent obligations, then as a consequence you
|
| 206 |
+
may not distribute the Program at all. For example, if a patent
|
| 207 |
+
license would not permit royalty-free redistribution of the Program by
|
| 208 |
+
all those who receive copies directly or indirectly through you, then
|
| 209 |
+
the only way you could satisfy both it and this License would be to
|
| 210 |
+
refrain entirely from distribution of the Program.
|
| 211 |
+
|
| 212 |
+
If any portion of this section is held invalid or unenforceable under
|
| 213 |
+
any particular circumstance, the balance of the section is intended to
|
| 214 |
+
apply and the section as a whole is intended to apply in other
|
| 215 |
+
circumstances.
|
| 216 |
+
|
| 217 |
+
It is not the purpose of this section to induce you to infringe any
|
| 218 |
+
patents or other property right claims or to contest validity of any
|
| 219 |
+
such claims; this section has the sole purpose of protecting the
|
| 220 |
+
integrity of the free software distribution system, which is
|
| 221 |
+
implemented by public license practices. Many people have made
|
| 222 |
+
generous contributions to the wide range of software distributed
|
| 223 |
+
through that system in reliance on consistent application of that
|
| 224 |
+
system; it is up to the author/donor to decide if he or she is willing
|
| 225 |
+
to distribute software through any other system and a licensee cannot
|
| 226 |
+
impose that choice.
|
| 227 |
+
|
| 228 |
+
This section is intended to make thoroughly clear what is believed to
|
| 229 |
+
be a consequence of the rest of this License.
|
| 230 |
+
|
| 231 |
+
8. If the distribution and/or use of the Program is restricted in
|
| 232 |
+
certain countries either by patents or by copyrighted interfaces, the
|
| 233 |
+
original copyright holder who places the Program under this License
|
| 234 |
+
may add an explicit geographical distribution limitation excluding
|
| 235 |
+
those countries, so that distribution is permitted only in or among
|
| 236 |
+
countries not thus excluded. In such case, this License incorporates
|
| 237 |
+
the limitation as if written in the body of this License.
|
| 238 |
+
|
| 239 |
+
9. The Free Software Foundation may publish revised and/or new versions
|
| 240 |
+
of the General Public License from time to time. Such new versions will
|
| 241 |
+
be similar in spirit to the present version, but may differ in detail to
|
| 242 |
+
address new problems or concerns.
|
| 243 |
+
|
| 244 |
+
Each version is given a distinguishing version number. If the Program
|
| 245 |
+
specifies a version number of this License which applies to it and "any
|
| 246 |
+
later version", you have the option of following the terms and conditions
|
| 247 |
+
either of that version or of any later version published by the Free
|
| 248 |
+
Software Foundation. If the Program does not specify a version number of
|
| 249 |
+
this License, you may choose any version ever published by the Free Software
|
| 250 |
+
Foundation.
|
| 251 |
+
|
| 252 |
+
10. If you wish to incorporate parts of the Program into other free
|
| 253 |
+
programs whose distribution conditions are different, write to the author
|
| 254 |
+
to ask for permission. For software which is copyrighted by the Free
|
| 255 |
+
Software Foundation, write to the Free Software Foundation; we sometimes
|
| 256 |
+
make exceptions for this. Our decision will be guided by the two goals
|
| 257 |
+
of preserving the free status of all derivatives of our free software and
|
| 258 |
+
of promoting the sharing and reuse of software generally.
|
| 259 |
+
|
| 260 |
+
NO WARRANTY
|
| 261 |
+
|
| 262 |
+
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
| 263 |
+
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
| 264 |
+
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
| 265 |
+
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
| 266 |
+
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
| 267 |
+
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
| 268 |
+
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
| 269 |
+
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
| 270 |
+
REPAIR OR CORRECTION.
|
| 271 |
+
|
| 272 |
+
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
| 273 |
+
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
| 274 |
+
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
| 275 |
+
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
| 276 |
+
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
| 277 |
+
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
| 278 |
+
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
| 279 |
+
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
| 280 |
+
POSSIBILITY OF SUCH DAMAGES.
|
| 281 |
+
|
| 282 |
+
END OF TERMS AND CONDITIONS
|
| 283 |
+
|
| 284 |
+
How to Apply These Terms to Your New Programs
|
| 285 |
+
|
| 286 |
+
If you develop a new program, and you want it to be of the greatest
|
| 287 |
+
possible use to the public, the best way to achieve this is to make it
|
| 288 |
+
free software which everyone can redistribute and change under these terms.
|
| 289 |
+
|
| 290 |
+
To do so, attach the following notices to the program. It is safest
|
| 291 |
+
to attach them to the start of each source file to most effectively
|
| 292 |
+
convey the exclusion of warranty; and each file should have at least
|
| 293 |
+
the "copyright" line and a pointer to where the full notice is found.
|
| 294 |
+
|
| 295 |
+
<one line to give the program's name and a brief idea of what it does.>
|
| 296 |
+
Copyright (C) <year> <name of author>
|
| 297 |
+
|
| 298 |
+
This program is free software; you can redistribute it and/or modify
|
| 299 |
+
it under the terms of the GNU General Public License as published by
|
| 300 |
+
the Free Software Foundation; either version 2 of the License, or
|
| 301 |
+
(at your option) any later version.
|
| 302 |
+
|
| 303 |
+
This program is distributed in the hope that it will be useful,
|
| 304 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 305 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 306 |
+
GNU General Public License for more details.
|
| 307 |
+
|
| 308 |
+
You should have received a copy of the GNU General Public License along
|
| 309 |
+
with this program; if not, see <https://www.gnu.org/licenses/>.
|
| 310 |
+
|
| 311 |
+
Also add information on how to contact you by electronic and paper mail.
|
| 312 |
+
|
| 313 |
+
If the program is interactive, make it output a short notice like this
|
| 314 |
+
when it starts in an interactive mode:
|
| 315 |
+
|
| 316 |
+
Gnomovision version 69, Copyright (C) year name of author
|
| 317 |
+
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
| 318 |
+
This is free software, and you are welcome to redistribute it
|
| 319 |
+
under certain conditions; type `show c' for details.
|
| 320 |
+
|
| 321 |
+
The hypothetical commands `show w' and `show c' should show the appropriate
|
| 322 |
+
parts of the General Public License. Of course, the commands you use may
|
| 323 |
+
be called something other than `show w' and `show c'; they could even be
|
| 324 |
+
mouse-clicks or menu items--whatever suits your program.
|
| 325 |
+
|
| 326 |
+
You should also get your employer (if you work as a programmer) or your
|
| 327 |
+
school, if any, to sign a "copyright disclaimer" for the program, if
|
| 328 |
+
necessary. Here is a sample; alter the names:
|
| 329 |
+
|
| 330 |
+
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
| 331 |
+
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
| 332 |
+
|
| 333 |
+
<signature of Moe Ghoul>, 1 April 1989
|
| 334 |
+
Moe Ghoul, President of Vice
|
| 335 |
+
|
| 336 |
+
This General Public License does not permit incorporating your program into
|
| 337 |
+
proprietary programs. If your program is a subroutine library, you may
|
| 338 |
+
consider it more useful to permit linking proprietary applications with the
|
| 339 |
+
library. If this is what you want to do, use the GNU Lesser General
|
| 340 |
+
Public License instead of this License.
|
docker-compose.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Install No Code Architect Toolkit with Docker
|
| 2 |
+
|
| 3 |
+
Installation of No Code Architect Toolkit with Docker offers the following advantages:
|
| 4 |
+
- Install No Code Architect Toolkit in a clean environment.
|
| 5 |
+
- Simplify the setup process.
|
| 6 |
+
- Avoid compatibility issues across different operating systems with Docker's consistent environment.
|
| 7 |
+
|
| 8 |
+
> **Info**
|
| 9 |
+
> If your domain/subdomain is already pointed to the server, start at step 2.
|
| 10 |
+
> If you have already installed Docker and Docker-Compose, start at step 3.
|
| 11 |
+
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
## 1. DNS Setup
|
| 15 |
+
|
| 16 |
+
Point your domain/subdomain to the server. Add an A record to route the domain/subdomain accordingly:
|
| 17 |
+
|
| 18 |
+
- **Type**: A
|
| 19 |
+
- **Name**: The desired domain/subdomain
|
| 20 |
+
- **IP Address**: `<IP_OF_YOUR_SERVER>`
|
| 21 |
+
|
| 22 |
+
---
|
| 23 |
+
|
| 24 |
+
## 2. Install Docker
|
| 25 |
+
|
| 26 |
+
This can vary depending on the Linux distribution used. Below are instructions for Ubuntu:
|
| 27 |
+
|
| 28 |
+
### Set up Docker's APT Repository
|
| 29 |
+
|
| 30 |
+
```bash
|
| 31 |
+
# Add Docker's official GPG key:
|
| 32 |
+
sudo apt-get update
|
| 33 |
+
sudo apt-get install ca-certificates curl
|
| 34 |
+
sudo install -m 0755 -d /etc/apt/keyrings
|
| 35 |
+
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
| 36 |
+
sudo chmod a+r /etc/apt/keyrings/docker.asc
|
| 37 |
+
|
| 38 |
+
# Add the repository to APT sources:
|
| 39 |
+
echo \
|
| 40 |
+
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
|
| 41 |
+
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
|
| 42 |
+
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
| 43 |
+
sudo apt-get update
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
### Install the Docker Packages
|
| 47 |
+
|
| 48 |
+
```bash
|
| 49 |
+
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
---
|
| 53 |
+
|
| 54 |
+
## 3. Create Docker Compose File
|
| 55 |
+
|
| 56 |
+
Create a `docker-compose.yml` file and paste the following configuration:
|
| 57 |
+
|
| 58 |
+
### With SSL Support
|
| 59 |
+
Enables SSL/TLS for secure, encrypted communications. Ideal for those wanting a hands-off approach to SSL setup.
|
| 60 |
+
|
| 61 |
+
```yaml
|
| 62 |
+
services:
|
| 63 |
+
traefik:
|
| 64 |
+
image: "traefik"
|
| 65 |
+
restart: unless-stopped
|
| 66 |
+
command:
|
| 67 |
+
- "--api=true"
|
| 68 |
+
- "--api.insecure=true"
|
| 69 |
+
- "--providers.docker=true"
|
| 70 |
+
- "--providers.docker.exposedbydefault=false"
|
| 71 |
+
- "--entrypoints.web.address=:80"
|
| 72 |
+
- "--entrypoints.web.http.redirections.entryPoint.to=websecure"
|
| 73 |
+
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
|
| 74 |
+
- "--entrypoints.websecure.address=:443"
|
| 75 |
+
- "--certificatesresolvers.mytlschallenge.acme.tlschallenge=true"
|
| 76 |
+
- "--certificatesresolvers.mytlschallenge.acme.email=${SSL_EMAIL}"
|
| 77 |
+
- "--certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json"
|
| 78 |
+
ports:
|
| 79 |
+
- "80:80"
|
| 80 |
+
- "443:443"
|
| 81 |
+
volumes:
|
| 82 |
+
- traefik_data:/letsencrypt
|
| 83 |
+
- /var/run/docker.sock:/var/run/docker.sock:ro
|
| 84 |
+
ncat:
|
| 85 |
+
image: stephengpope/no-code-architects-toolkit:latest
|
| 86 |
+
env_file:
|
| 87 |
+
- .env
|
| 88 |
+
labels:
|
| 89 |
+
- traefik.enable=true
|
| 90 |
+
- traefik.http.routers.ncat.rule=Host(`${APP_DOMAIN}`)
|
| 91 |
+
- traefik.http.routers.ncat.tls=true
|
| 92 |
+
- traefik.http.routers.ncat.entrypoints=web,websecure
|
| 93 |
+
- traefik.http.routers.ncat.tls.certresolver=mytlschallenge
|
| 94 |
+
volumes:
|
| 95 |
+
- storage:/var/www/html/storage/app
|
| 96 |
+
- logs:/var/www/html/storage/logs
|
| 97 |
+
restart: unless-stopped
|
| 98 |
+
|
| 99 |
+
volumes:
|
| 100 |
+
traefik_data:
|
| 101 |
+
driver: local
|
| 102 |
+
storage:
|
| 103 |
+
driver: local
|
| 104 |
+
logs:
|
| 105 |
+
driver: local
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
---
|
| 109 |
+
|
| 110 |
+
## 4. Create `.env` File
|
| 111 |
+
|
| 112 |
+
Create an `.env` file and configure it accordingly:
|
| 113 |
+
|
| 114 |
+
```env
|
| 115 |
+
# The name of your application.
|
| 116 |
+
APP_NAME=NCAToolkit
|
| 117 |
+
|
| 118 |
+
# Debug mode setting. Set to `false` for production environments.
|
| 119 |
+
APP_DEBUG=false
|
| 120 |
+
|
| 121 |
+
# Your app's domain or subdomain, without the 'http://' or 'https://' prefix.
|
| 122 |
+
APP_DOMAIN=example.com
|
| 123 |
+
|
| 124 |
+
# Full application URL is automatically configured; no modification required.
|
| 125 |
+
APP_URL=https://${APP_DOMAIN}
|
| 126 |
+
|
| 127 |
+
# SSL settings
|
| 128 |
+
SSL_EMAIL=user@example.com
|
| 129 |
+
|
| 130 |
+
# API_KEY
|
| 131 |
+
# Purpose: Used for API authentication.
|
| 132 |
+
# Requirement: Mandatory.
|
| 133 |
+
API_KEY=your_api_key_here
|
| 134 |
+
|
| 135 |
+
# s3 Compatible Storage Env Vars
|
| 136 |
+
#
|
| 137 |
+
#S3_ACCESS_KEY=your_access_key
|
| 138 |
+
#S3_SECRET_KEY=your_secret_key
|
| 139 |
+
#S3_ENDPOINT_URL=https://your-endpoint-url
|
| 140 |
+
#S3_REGION=your-region
|
| 141 |
+
#S3_BUCKET_NAME=your-bucket-name
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
# Google Cloud Storage Env Variables
|
| 145 |
+
#
|
| 146 |
+
# GCP_SA_CREDENTIALS
|
| 147 |
+
# Purpose: The JSON credentials for the GCP Service Account.
|
| 148 |
+
# Requirement: Mandatory if using GCP storage.
|
| 149 |
+
#GCP_SA_CREDENTIALS=/path/to/your/gcp/service_account.json
|
| 150 |
+
|
| 151 |
+
# GCP_BUCKET_NAME
|
| 152 |
+
# Purpose: The name of the GCP storage bucket.
|
| 153 |
+
# Requirement: Mandatory if using GCP storage.
|
| 154 |
+
#GCP_BUCKET_NAME=your_gcp_bucket_name
|
| 155 |
+
|
| 156 |
+
# STORAGE_PATH
|
| 157 |
+
# Purpose: The base path for storage operations.
|
| 158 |
+
# Default: GCP
|
| 159 |
+
# Requirement: Optional.
|
| 160 |
+
#STORAGE_PATH=GCP
|
| 161 |
+
|
| 162 |
+
```
|
| 163 |
+
|
| 164 |
+
---
|
| 165 |
+
|
| 166 |
+
## 5. Start Docker Compose
|
| 167 |
+
|
| 168 |
+
Start No Code Architect Toolkit using the following command:
|
| 169 |
+
|
| 170 |
+
```bash
|
| 171 |
+
docker compose up -d
|
| 172 |
+
```
|
| 173 |
+
|
| 174 |
+
To view logs in real time:
|
| 175 |
+
|
| 176 |
+
```bash
|
| 177 |
+
docker compose logs -f
|
| 178 |
+
```
|
| 179 |
+
|
| 180 |
+
To stop the containers:
|
| 181 |
+
|
| 182 |
+
```bash
|
| 183 |
+
docker compose stop
|
| 184 |
+
```
|
| 185 |
+
|
| 186 |
+
To restart and reload env vars
|
| 187 |
+
|
| 188 |
+
# First update your .env file with the correct values
|
| 189 |
+
# Then run:
|
| 190 |
+
|
| 191 |
+
```bash
|
| 192 |
+
docker compose up -d --force-recreate ncat
|
| 193 |
+
```
|
| 194 |
+
|
| 195 |
+
---
|
| 196 |
+
|
| 197 |
+
## 6. Done
|
| 198 |
+
|
| 199 |
+
No Code Architect Toolkit is now accessible through the specified APP_URL. For example:
|
| 200 |
+
[https://example.com](https://example.com)
|
docs/adding_routes.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Adding New Routes
|
| 2 |
+
|
| 3 |
+
This document explains how to add new routes to the application using the dynamic route registration system.
|
| 4 |
+
|
| 5 |
+
## Overview
|
| 6 |
+
|
| 7 |
+
The application now uses a dynamic route registration system that automatically discovers and registers all Flask blueprints in the `routes` directory. This means you no longer need to manually import and register blueprints in `app.py`.
|
| 8 |
+
|
| 9 |
+
## How to Add a New Route
|
| 10 |
+
|
| 11 |
+
1. **Create a new route file**
|
| 12 |
+
|
| 13 |
+
Create a new Python file in the appropriate location in the `routes` directory. For a v1 API endpoint, you would typically place it in a subdirectory under `routes/v1/` based on the functionality.
|
| 14 |
+
|
| 15 |
+
For example:
|
| 16 |
+
```
|
| 17 |
+
routes/v1/email/send_email.py
|
| 18 |
+
```
|
| 19 |
+
|
| 20 |
+
2. **Define your Blueprint**
|
| 21 |
+
|
| 22 |
+
In your route file, define a Flask Blueprint with a unique name. Make sure to follow the naming convention:
|
| 23 |
+
|
| 24 |
+
```python
|
| 25 |
+
# routes/v1/email/send_email.py
|
| 26 |
+
from flask import Blueprint, request
|
| 27 |
+
from services.authentication import authenticate
|
| 28 |
+
from app_utils import queue_task_wrapper
|
| 29 |
+
|
| 30 |
+
v1_email_send_bp = Blueprint('v1_email_send', __name__)
|
| 31 |
+
|
| 32 |
+
@v1_email_send_bp.route('/v1/email/send', methods=['POST'])
|
| 33 |
+
@authenticate
|
| 34 |
+
@queue_task_wrapper(bypass_queue=False)
|
| 35 |
+
def send_email(job_id, data):
|
| 36 |
+
"""
|
| 37 |
+
Send an email
|
| 38 |
+
|
| 39 |
+
Args:
|
| 40 |
+
job_id (str): Job ID assigned by queue_task_wrapper
|
| 41 |
+
data (dict): Request data containing email details
|
| 42 |
+
|
| 43 |
+
Returns:
|
| 44 |
+
Tuple of (response_data, endpoint_string, status_code)
|
| 45 |
+
"""
|
| 46 |
+
# Your implementation here
|
| 47 |
+
endpoint = "/v1/email/send"
|
| 48 |
+
|
| 49 |
+
# Return response
|
| 50 |
+
return {"message": "Email sent"}, endpoint, 200
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
3. **That's it!**
|
| 54 |
+
|
| 55 |
+
No need to modify `app.py`. The blueprint will be automatically discovered and registered when the application starts.
|
| 56 |
+
|
| 57 |
+
## Naming Conventions
|
| 58 |
+
|
| 59 |
+
When creating new routes, please follow these naming conventions:
|
| 60 |
+
|
| 61 |
+
1. **Blueprint names**: Use the format `{version}_{category}_{action}_bp`
|
| 62 |
+
- Example: `v1_email_send_bp` for sending emails
|
| 63 |
+
|
| 64 |
+
2. **Route paths**: Use the format `/{version}/{category}/{action}`
|
| 65 |
+
- Example: `/v1/email/send`
|
| 66 |
+
|
| 67 |
+
3. **File structure**: Place files in directories that match the route structure
|
| 68 |
+
- Example: `routes/v1/email/send_email.py`
|
| 69 |
+
|
| 70 |
+
## Testing Your Route
|
| 71 |
+
|
| 72 |
+
After adding your route, restart the application and your new endpoint should be available immediately.
|
| 73 |
+
|
| 74 |
+
## Troubleshooting
|
| 75 |
+
|
| 76 |
+
If your route isn't being registered:
|
| 77 |
+
|
| 78 |
+
1. Check logs for any import errors
|
| 79 |
+
2. Ensure your blueprint variable is defined at the module level
|
| 80 |
+
3. Verify the blueprint name follows the naming convention
|
| 81 |
+
4. Make sure your Python file is in the correct directory under `routes/`
|
docs/audio/concatenate.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Audio Concatenation API Endpoint Documentation
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
The `/v1/audio/concatenate` endpoint provides functionality to combine multiple audio files into a single audio file. This endpoint is part of the v1 API structure and is registered in the main application through the `v1_audio_concatenate_bp` Blueprint. It leverages the application's queuing system to handle asynchronous processing, which is particularly useful for potentially time-consuming audio processing operations.
|
| 6 |
+
|
| 7 |
+
## Endpoint
|
| 8 |
+
|
| 9 |
+
- **URL**: `/v1/audio/concatenate`
|
| 10 |
+
- **Method**: `POST`
|
| 11 |
+
|
| 12 |
+
## Request
|
| 13 |
+
|
| 14 |
+
### Headers
|
| 15 |
+
|
| 16 |
+
- `x-api-key`: Required. Your API authentication key.
|
| 17 |
+
|
| 18 |
+
### Body Parameters
|
| 19 |
+
|
| 20 |
+
| Parameter | Type | Required | Description |
|
| 21 |
+
|-----------|------|----------|-------------|
|
| 22 |
+
| `audio_urls` | Array | Yes | An array of objects, each containing an `audio_url` property pointing to an audio file to be concatenated. Must contain at least one item. |
|
| 23 |
+
| `webhook_url` | String | No | A URL to receive a callback notification when processing is complete. If provided, the request will be processed asynchronously. |
|
| 24 |
+
| `id` | String | No | A custom identifier for tracking the request. |
|
| 25 |
+
|
| 26 |
+
Each object in the `audio_urls` array must have:
|
| 27 |
+
- `audio_url`: String (URI format). The URL of an audio file to be concatenated.
|
| 28 |
+
|
| 29 |
+
### Example Request
|
| 30 |
+
|
| 31 |
+
```json
|
| 32 |
+
{
|
| 33 |
+
"audio_urls": [
|
| 34 |
+
{ "audio_url": "https://example.com/audio1.mp3" },
|
| 35 |
+
{ "audio_url": "https://example.com/audio2.mp3" },
|
| 36 |
+
{ "audio_url": "https://example.com/audio3.mp3" }
|
| 37 |
+
],
|
| 38 |
+
"webhook_url": "https://your-webhook-endpoint.com/callback",
|
| 39 |
+
"id": "custom-request-id-123"
|
| 40 |
+
}
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
### Example cURL Command
|
| 44 |
+
|
| 45 |
+
```bash
|
| 46 |
+
curl -X POST \
|
| 47 |
+
https://api.example.com/v1/audio/concatenate \
|
| 48 |
+
-H 'Content-Type: application/json' \
|
| 49 |
+
-H 'x-api-key: your-api-key-here' \
|
| 50 |
+
-d '{
|
| 51 |
+
"audio_urls": [
|
| 52 |
+
{ "audio_url": "https://example.com/audio1.mp3" },
|
| 53 |
+
{ "audio_url": "https://example.com/audio2.mp3" }
|
| 54 |
+
],
|
| 55 |
+
"webhook_url": "https://your-webhook-endpoint.com/callback",
|
| 56 |
+
"id": "custom-request-id-123"
|
| 57 |
+
}'
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
## Response
|
| 61 |
+
|
| 62 |
+
### Synchronous Response (No webhook_url provided)
|
| 63 |
+
|
| 64 |
+
If no `webhook_url` is provided, the request will be processed synchronously and return:
|
| 65 |
+
|
| 66 |
+
```json
|
| 67 |
+
{
|
| 68 |
+
"code": 200,
|
| 69 |
+
"id": "custom-request-id-123",
|
| 70 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 71 |
+
"response": "https://storage.example.com/combined-audio-file.mp3",
|
| 72 |
+
"message": "success",
|
| 73 |
+
"run_time": 2.345,
|
| 74 |
+
"queue_time": 0,
|
| 75 |
+
"total_time": 2.345,
|
| 76 |
+
"pid": 12345,
|
| 77 |
+
"queue_id": 67890,
|
| 78 |
+
"queue_length": 0,
|
| 79 |
+
"build_number": "1.0.123"
|
| 80 |
+
}
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
### Asynchronous Response (webhook_url provided)
|
| 84 |
+
|
| 85 |
+
If a `webhook_url` is provided, the request will be queued for processing and immediately return:
|
| 86 |
+
|
| 87 |
+
```json
|
| 88 |
+
{
|
| 89 |
+
"code": 202,
|
| 90 |
+
"id": "custom-request-id-123",
|
| 91 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 92 |
+
"message": "processing",
|
| 93 |
+
"pid": 12345,
|
| 94 |
+
"queue_id": 67890,
|
| 95 |
+
"max_queue_length": "unlimited",
|
| 96 |
+
"queue_length": 1,
|
| 97 |
+
"build_number": "1.0.123"
|
| 98 |
+
}
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
When processing is complete, a webhook will be sent to the provided URL with the following payload:
|
| 102 |
+
|
| 103 |
+
```json
|
| 104 |
+
{
|
| 105 |
+
"endpoint": "/v1/audio/concatenate",
|
| 106 |
+
"code": 200,
|
| 107 |
+
"id": "custom-request-id-123",
|
| 108 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 109 |
+
"response": "https://storage.example.com/combined-audio-file.mp3",
|
| 110 |
+
"message": "success",
|
| 111 |
+
"pid": 12345,
|
| 112 |
+
"queue_id": 67890,
|
| 113 |
+
"run_time": 3.456,
|
| 114 |
+
"queue_time": 1.234,
|
| 115 |
+
"total_time": 4.690,
|
| 116 |
+
"queue_length": 0,
|
| 117 |
+
"build_number": "1.0.123"
|
| 118 |
+
}
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
### Error Responses
|
| 122 |
+
|
| 123 |
+
#### Invalid Request Format (400 Bad Request)
|
| 124 |
+
|
| 125 |
+
```json
|
| 126 |
+
{
|
| 127 |
+
"code": 400,
|
| 128 |
+
"id": null,
|
| 129 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 130 |
+
"message": "Invalid request: 'audio_urls' is a required property",
|
| 131 |
+
"pid": 12345,
|
| 132 |
+
"queue_id": 67890,
|
| 133 |
+
"queue_length": 0,
|
| 134 |
+
"build_number": "1.0.123"
|
| 135 |
+
}
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
#### Authentication Error (401 Unauthorized)
|
| 139 |
+
|
| 140 |
+
```json
|
| 141 |
+
{
|
| 142 |
+
"code": 401,
|
| 143 |
+
"message": "Invalid or missing API key",
|
| 144 |
+
"build_number": "1.0.123"
|
| 145 |
+
}
|
| 146 |
+
```
|
| 147 |
+
|
| 148 |
+
#### Queue Limit Reached (429 Too Many Requests)
|
| 149 |
+
|
| 150 |
+
```json
|
| 151 |
+
{
|
| 152 |
+
"code": 429,
|
| 153 |
+
"id": "custom-request-id-123",
|
| 154 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 155 |
+
"message": "MAX_QUEUE_LENGTH (100) reached",
|
| 156 |
+
"pid": 12345,
|
| 157 |
+
"queue_id": 67890,
|
| 158 |
+
"queue_length": 100,
|
| 159 |
+
"build_number": "1.0.123"
|
| 160 |
+
}
|
| 161 |
+
```
|
| 162 |
+
|
| 163 |
+
#### Processing Error (500 Internal Server Error)
|
| 164 |
+
|
| 165 |
+
```json
|
| 166 |
+
{
|
| 167 |
+
"code": 500,
|
| 168 |
+
"id": "custom-request-id-123",
|
| 169 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 170 |
+
"message": "Error downloading audio file: Connection refused",
|
| 171 |
+
"pid": 12345,
|
| 172 |
+
"queue_id": 67890,
|
| 173 |
+
"queue_length": 0,
|
| 174 |
+
"build_number": "1.0.123"
|
| 175 |
+
}
|
| 176 |
+
```
|
| 177 |
+
|
| 178 |
+
## Error Handling
|
| 179 |
+
|
| 180 |
+
- **Missing Required Parameters**: If `audio_urls` is missing or empty, a 400 Bad Request response will be returned.
|
| 181 |
+
- **Invalid URL Format**: If any `audio_url` is not a valid URI, a 400 Bad Request response will be returned.
|
| 182 |
+
- **Authentication Failure**: If the API key is invalid or missing, a 401 Unauthorized response will be returned.
|
| 183 |
+
- **Queue Limit**: If the queue is full (when MAX_QUEUE_LENGTH is set), a 429 Too Many Requests response will be returned.
|
| 184 |
+
- **Processing Errors**: Any errors during audio download, processing, or upload will result in a 500 Internal Server Error response with details in the message field.
|
| 185 |
+
|
| 186 |
+
## Usage Notes
|
| 187 |
+
|
| 188 |
+
1. **Asynchronous Processing**: For long audio files, it's recommended to use the `webhook_url` parameter to process the request asynchronously.
|
| 189 |
+
2. **File Formats**: The service supports common audio formats. The output will be in a standard format (typically MP3).
|
| 190 |
+
3. **File Size**: There may be limits on the size of audio files that can be processed. Very large files might cause timeouts or failures.
|
| 191 |
+
4. **Queue Behavior**: If the system is under heavy load, requests with `webhook_url` will be queued. The MAX_QUEUE_LENGTH environment variable controls the maximum queue size.
|
| 192 |
+
|
| 193 |
+
## Common Issues
|
| 194 |
+
|
| 195 |
+
1. **Inaccessible Audio URLs**: Ensure all audio URLs are publicly accessible. Private or authentication-required URLs will cause failures.
|
| 196 |
+
2. **Incompatible Audio Formats**: Some exotic audio formats might not be supported. Stick to common formats like MP3, WAV, or AAC.
|
| 197 |
+
3. **Webhook Failures**: If your webhook endpoint is unavailable when the processing completes, you might not receive the completion notification.
|
| 198 |
+
4. **Timeout Issues**: Very large audio files might cause timeouts during download or processing.
|
| 199 |
+
|
| 200 |
+
## Best Practices
|
| 201 |
+
|
| 202 |
+
1. **Use Webhooks for Large Files**: Always use the webhook approach for large audio files or when concatenating many files.
|
| 203 |
+
2. **Include an ID**: Always include a custom `id` parameter to help track your requests, especially in webhook responses.
|
| 204 |
+
3. **Error Handling**: Implement robust error handling in your client application to handle various HTTP status codes.
|
| 205 |
+
4. **Webhook Reliability**: Ensure your webhook endpoint is reliable and can handle retries if necessary.
|
| 206 |
+
5. **File Preparation**: Pre-process your audio files to ensure they have compatible formats, sample rates, and channel configurations for best results.
|
| 207 |
+
6. **Queue Monitoring**: Monitor the `queue_length` in responses to understand system load and adjust your request patterns if needed.
|
docs/cloud-installation/do.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Installing on Digital Ocean
|
| 2 |
+
|
| 3 |
+
This guide walks you through deploying the No-Code Architects Toolkit API on Digital Ocean's App Platform.
|
| 4 |
+
|
| 5 |
+
## Prerequisites
|
| 6 |
+
|
| 7 |
+
- A Digital Ocean account ([Sign up here](https://www.digitalocean.com/))
|
| 8 |
+
- Basic familiarity with Digital Ocean App Platform
|
| 9 |
+
- A credit/debit card for billing (you'll only be charged for what you use)
|
| 10 |
+
|
| 11 |
+
## Step 1: Create a New Project
|
| 12 |
+
|
| 13 |
+
1. Sign in to your Digital Ocean account
|
| 14 |
+
2. Create a new project or select an existing one
|
| 15 |
+
3. This will organize your resources for the NCA Toolkit
|
| 16 |
+
|
| 17 |
+
## Step 2: Create a Digital Ocean Space
|
| 18 |
+
|
| 19 |
+
You'll need to create a Space (Digital Ocean's object storage) for the toolkit to store processed files:
|
| 20 |
+
|
| 21 |
+
1. Navigate to **Spaces Object Storage** in the Digital Ocean dashboard
|
| 22 |
+
2. Click **Create a Space**
|
| 23 |
+
3. Select a region (e.g., New York)
|
| 24 |
+
4. Give your bucket a name (e.g., `nca-toolkit-bucket`)
|
| 25 |
+
5. Select your project
|
| 26 |
+
6. Click **Create Space**
|
| 27 |
+
|
| 28 |
+
## Step 3: Generate API Keys for Your Space
|
| 29 |
+
|
| 30 |
+
1. From your new Space, go to **Settings**
|
| 31 |
+
2. Click **Create Access Key**
|
| 32 |
+
3. Select **Full Access**
|
| 33 |
+
4. Give your key a name (e.g., `nca-toolkit-key`)
|
| 34 |
+
5. Click **Create Access Key**
|
| 35 |
+
6. **IMPORTANT**: Save both the Access Key and Secret Key shown - you will only see them once!
|
| 36 |
+
7. Also copy the Space URL (endpoint) for use in the next step
|
| 37 |
+
|
| 38 |
+
## Step 4: Deploy the App
|
| 39 |
+
|
| 40 |
+
1. From your Digital Ocean dashboard, click **Create** and select **App**
|
| 41 |
+
2. Choose **Container Image** as the deployment source
|
| 42 |
+
3. Select **Docker Hub** for the repository
|
| 43 |
+
4. Enter `stephengpope/no-code-architects-toolkit` as the image name
|
| 44 |
+
5. Enter `latest` for the image tag
|
| 45 |
+
6. Click **Next**
|
| 46 |
+
7. If needed, edit the name to remove any extra dashes (Digital Ocean may show an error for long names)
|
| 47 |
+
8. Choose **Web Service** as the service type
|
| 48 |
+
|
| 49 |
+
## Step 5: Configure Resources
|
| 50 |
+
|
| 51 |
+
1. Select a plan with adequate resources for your needs:
|
| 52 |
+
- For testing, a $50/month instance provides good performance
|
| 53 |
+
- For smaller workloads, you can select a smaller instance
|
| 54 |
+
- Note: You're only charged for the time the server is running
|
| 55 |
+
2. Set Containers to 1
|
| 56 |
+
3. Close the resource selection dialog
|
| 57 |
+
|
| 58 |
+
## Step 6: Configure Environment Variables
|
| 59 |
+
|
| 60 |
+
Add the following environment variables exactly as shown (be careful with underscores vs. dashes and avoid any leading/trailing spaces):
|
| 61 |
+
|
| 62 |
+
1. `API_KEY`: Your API key (e.g., `test123` for testing - change for production)
|
| 63 |
+
2. `S3_ENDPOINT_URL`: The URL of your Space (copied from Step 3)
|
| 64 |
+
3. `S3_ACCESS_KEY`: The access key from Step 3
|
| 65 |
+
4. `S3_SECRET_KEY`: The secret key from Step 3
|
| 66 |
+
5. `S3_BUCKET_NAME`: The name of your Space bucket (e.g., `nca-toolkit-bucket`)
|
| 67 |
+
6. `S3_REGION`: The region code of your Space (e.g., `NYC3` for New York)
|
| 68 |
+
|
| 69 |
+
## Step 7: Finalize and Deploy
|
| 70 |
+
|
| 71 |
+
1. For Deployment Region, select a region close to your location (e.g., San Francisco)
|
| 72 |
+
2. You can use the default app name or choose a custom name
|
| 73 |
+
3. Click **Create Resource**
|
| 74 |
+
4. Wait for the deployment to complete (this may take a few minutes)
|
| 75 |
+
- You may need to refresh the page to see updates
|
| 76 |
+
|
| 77 |
+
## Step 8: Test Your Deployment
|
| 78 |
+
|
| 79 |
+
### Using Postman
|
| 80 |
+
|
| 81 |
+
1. Sign up for or log in to [Postman](https://www.postman.com/)
|
| 82 |
+
2. Import the [NCA Toolkit Postman Collection](https://bit.ly/49Gkh61)
|
| 83 |
+
3. Fork the collection to your workspace
|
| 84 |
+
4. Create a new environment:
|
| 85 |
+
- Name it "Digital Ocean" or similar
|
| 86 |
+
- Add a variable `x-api-key` with the value matching your API_KEY (e.g., `test123`)
|
| 87 |
+
- Add a variable `base_url` with the value of your app's URL (shown in the Digital Ocean dashboard)
|
| 88 |
+
- Save the environment
|
| 89 |
+
5. In the collection, navigate to the `toolkit/authenticate` endpoint and click Send
|
| 90 |
+
6. If you receive a success response, your deployment is working correctly
|
| 91 |
+
7. Then test the `toolkit/test` endpoint to verify complete functionality
|
| 92 |
+
|
| 93 |
+
## Monitoring and Management
|
| 94 |
+
|
| 95 |
+
- **Overview**: View basic information about your app
|
| 96 |
+
- **Insights**: Monitor CPU and memory usage
|
| 97 |
+
- **Runtime Logs**: View logs of API calls and server activity
|
| 98 |
+
- **Console**: Access the server's command line (rarely needed)
|
| 99 |
+
- **Settings**: Modify your app's configuration
|
| 100 |
+
|
| 101 |
+
## Next Steps
|
| 102 |
+
|
| 103 |
+
Now that you have successfully deployed the NCA Toolkit API, you can:
|
| 104 |
+
- Explore all the available endpoints in the Postman collection
|
| 105 |
+
- Integrate the API with your applications
|
| 106 |
+
- Consider securing your API key with a more complex value
|
| 107 |
+
- Scale your resources up or down based on your usage requirements
|
| 108 |
+
|
| 109 |
+
Remember, Digital Ocean charges based on usage, so you can always delete the app when you're not using it to save costs.
|
docs/cloud-installation/gcp.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Installing on the Google Cloud Platform (GCP)
|
| 2 |
+
|
| 3 |
+
## 🎥 Video Instructions
|
| 4 |
+
|
| 5 |
+
Watch **[Detailed Video Instructions](https://youtu.be/6bC93sek9v8)** to set up the No-Code Architects Toolkit API.
|
| 6 |
+
|
| 7 |
+
- Use the **Docker Image** below:
|
| 8 |
+
|
| 9 |
+
```
|
| 10 |
+
stephengpope/no-code-architects-toolkit:latest
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
### Video Resources
|
| 14 |
+
|
| 15 |
+
- **[Postman Template](https://bit.ly/49Gkh61)**
|
| 16 |
+
- **[NCA Toolkit API GPT](https://bit.ly/4feDDk4)**
|
| 17 |
+
|
| 18 |
+
Or use the guide below walks you through the steps to install the NCA Toolkit API on GCP.
|
| 19 |
+
|
| 20 |
+
---
|
| 21 |
+
|
| 22 |
+
## **Prerequisites**
|
| 23 |
+
- A Google Cloud account. [Sign up here](https://cloud.google.com/) if you don't already have one.
|
| 24 |
+
- New users receive $300 in free credits.
|
| 25 |
+
- Basic knowledge of GCP services such as Cloud Run and Cloud Storage.
|
| 26 |
+
- A terminal or code editor for managing files.
|
| 27 |
+
|
| 28 |
+
---
|
| 29 |
+
|
| 30 |
+
## **Step 1: Create a Google Cloud Project**
|
| 31 |
+
1. Log into the [GCP Console](https://console.cloud.google.com/).
|
| 32 |
+
2. Click on the **Project Selector** in the top navigation bar and select **New Project**.
|
| 33 |
+
3. Enter a project name, such as `NCA Toolkit Project`.
|
| 34 |
+
4. Click **Create**.
|
| 35 |
+
|
| 36 |
+
---
|
| 37 |
+
|
| 38 |
+
## **Step 2: Enable Required APIs**
|
| 39 |
+
Enable the following APIs:
|
| 40 |
+
- **Cloud Storage API**
|
| 41 |
+
- **Cloud Storage JSON API**
|
| 42 |
+
- **Cloud Run API**
|
| 43 |
+
|
| 44 |
+
### **How to Enable APIs:**
|
| 45 |
+
1. In the GCP Console, navigate to **APIs & Services** > **Enable APIs and Services**.
|
| 46 |
+
2. Search for each API, click on it, and enable it.
|
| 47 |
+
|
| 48 |
+
---
|
| 49 |
+
|
| 50 |
+
## **Step 3: Create a Service Account**
|
| 51 |
+
1. Navigate to **IAM & Admin** > **Service Accounts** in the GCP Console.
|
| 52 |
+
2. Click **+ Create Service Account**.
|
| 53 |
+
- Enter a name (e.g., `NCA Toolkit Service Account`).
|
| 54 |
+
3. Assign the following roles to the service account:
|
| 55 |
+
- **Storage Admin**
|
| 56 |
+
- **Viewer**
|
| 57 |
+
4. Click **Done** to create the service account.
|
| 58 |
+
5. Open the service account details and navigate to the **Keys** tab.
|
| 59 |
+
- Click **Add Key** > **Create New Key**.
|
| 60 |
+
- Choose **JSON** format, download the file, and store it securely.
|
| 61 |
+
|
| 62 |
+
---
|
| 63 |
+
|
| 64 |
+
## **Step 4: Create a Cloud Storage Bucket**
|
| 65 |
+
1. Navigate to **Storage** > **Buckets** in the GCP Console.
|
| 66 |
+
2. Click **+ Create Bucket**.
|
| 67 |
+
- Choose a unique bucket name (e.g., `nca-toolkit-bucket`).
|
| 68 |
+
- Leave default settings, but:
|
| 69 |
+
- Uncheck **Enforce public access prevention**.
|
| 70 |
+
- Set **Access Control** to **Uniform**.
|
| 71 |
+
3. Click **Create** to finish.
|
| 72 |
+
4. Go to the bucket permissions, and add **allUsers** as a principal with the role:
|
| 73 |
+
- **Storage Object Viewer**.
|
| 74 |
+
5. Save changes.
|
| 75 |
+
|
| 76 |
+
---
|
| 77 |
+
|
| 78 |
+
## **Step 5: Deploy on Google Cloud Run**
|
| 79 |
+
|
| 80 |
+
### 1. Navigate to Cloud Run
|
| 81 |
+
- Open the **Cloud Run** service in the **Google Cloud Console**.
|
| 82 |
+
|
| 83 |
+
### 2. Create a New Service
|
| 84 |
+
- Click **Create Service**.
|
| 85 |
+
- Then **Deploy one revision from Docker Hub using the image below**:
|
| 86 |
+
|
| 87 |
+
```
|
| 88 |
+
stephengpope/no-code-architects-toolkit:latest
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
### 3. Allow Unauthenticated Invocations
|
| 92 |
+
- Check the box to **allow unauthenticated invocations**.
|
| 93 |
+
|
| 94 |
+
### 4. Configure Resource Allocation
|
| 95 |
+
- Set **Memory**: `16 GB`.
|
| 96 |
+
- Set **CPU**: `4 CPUs`.
|
| 97 |
+
- Set **CPU Allocation**: **Always Allocated**.
|
| 98 |
+
|
| 99 |
+
### 5. Adjust Scaling Settings
|
| 100 |
+
- **Minimum Instances**: `0` (to minimize cost during idle times).
|
| 101 |
+
- **Maximum Instances**: `5` (adjustable based on expected load).
|
| 102 |
+
|
| 103 |
+
### 6. Use Second-Generation Servers
|
| 104 |
+
- Scroll to **Platform Version** and select **Second Generation**.
|
| 105 |
+
- Second-generation servers offer better performance and feature support for advanced use cases.
|
| 106 |
+
|
| 107 |
+
### 7. Add Environment Variables
|
| 108 |
+
- Add the following environment variables:
|
| 109 |
+
- `API_KEY`: Your API key (e.g., `Test123`).
|
| 110 |
+
- `GCP_BUCKET_NAME`: The name of your Cloud Storage bucket.
|
| 111 |
+
- `GCP_SA_CREDENTIALS`: The JSON key of your service account.
|
| 112 |
+
- Paste the **entire contents** of the downloaded JSON key file into this field.
|
| 113 |
+
- Ensure:
|
| 114 |
+
- Proper JSON formatting.
|
| 115 |
+
- No leading or trailing spaces.
|
| 116 |
+
|
| 117 |
+
### 8. Configure Advanced Settings
|
| 118 |
+
- Set the **Container Port**: Default to `8080`.
|
| 119 |
+
- **Request Timeout**: `300 seconds` (to handle long-running requests).
|
| 120 |
+
- Enable **Startup Boost** to improve performance for the first request after a cold start.
|
| 121 |
+
|
| 122 |
+
### 9. Deploy the Service
|
| 123 |
+
- Verify all settings and click **Create**.
|
| 124 |
+
- The deployment process might take a few minutes. Once completed, a green checkmark should appear in the Cloud Run dashboard.
|
| 125 |
+
|
| 126 |
+
By following these steps, the NCA Toolkit will be successfully deployed and accessible via Google Cloud Run with second-generation servers for optimal performance.
|
| 127 |
+
|
| 128 |
+
---
|
| 129 |
+
|
| 130 |
+
## **Step 6: Test the Deployment**
|
| 131 |
+
|
| 132 |
+
1. Install **[Postman Template](https://bit.ly/49Gkh61)** on your computer.
|
| 133 |
+
2. Import the API example requests from the NCA Toolkit GitHub repository.
|
| 134 |
+
3. Configure two environment variables in Postman:
|
| 135 |
+
- `base_url`: Your deployed Cloud Run service URL.
|
| 136 |
+
- `x-api-key`: The API key you configured in **Step 5**.
|
| 137 |
+
4. Use the example requests to validate that the API is functioning correctly.
|
| 138 |
+
5. Use the **[NCA Toolkit API GPT](https://bit.ly/4feDDk4)** to learn more.
|
| 139 |
+
|
| 140 |
+
By following these steps, your NCA Toolkit API should be successfully deployed on Google Cloud Platform.
|
docs/code/execute/execute_python.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Execute Python Code Endpoint
|
| 2 |
+
|
| 3 |
+
## 1. Overview
|
| 4 |
+
|
| 5 |
+
The `/v1/code/execute/python` endpoint allows users to execute Python code on the server. This endpoint is part of the version 1.0 API structure defined in `app.py`. It is designed to provide a secure and controlled environment for executing Python code, with features like input validation, output capturing, and timeout handling.
|
| 6 |
+
|
| 7 |
+
## 2. Endpoint
|
| 8 |
+
|
| 9 |
+
**URL Path:** `/v1/code/execute/python`
|
| 10 |
+
**HTTP Method:** `POST`
|
| 11 |
+
|
| 12 |
+
## 3. Request
|
| 13 |
+
|
| 14 |
+
### Headers
|
| 15 |
+
|
| 16 |
+
- `x-api-key` (required): The API key for authentication.
|
| 17 |
+
|
| 18 |
+
### Body Parameters
|
| 19 |
+
|
| 20 |
+
The request body must be a JSON object with the following properties:
|
| 21 |
+
|
| 22 |
+
- `code` (string, required): The Python code to be executed.
|
| 23 |
+
- `timeout` (integer, optional): The maximum execution time in seconds, between 1 and 300. Default is 30 seconds.
|
| 24 |
+
- `webhook_url` (string, optional): The URL to receive the execution result via a webhook.
|
| 25 |
+
- `id` (string, optional): A unique identifier for the request.
|
| 26 |
+
|
| 27 |
+
The `validate_payload` directive in the routes file enforces the following JSON schema for the request body:
|
| 28 |
+
|
| 29 |
+
```json
|
| 30 |
+
{
|
| 31 |
+
"type": "object",
|
| 32 |
+
"properties": {
|
| 33 |
+
"code": {"type": "string"},
|
| 34 |
+
"timeout": {"type": "integer", "minimum": 1, "maximum": 300},
|
| 35 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 36 |
+
"id": {"type": "string"}
|
| 37 |
+
},
|
| 38 |
+
"required": ["code"],
|
| 39 |
+
"additionalProperties": False
|
| 40 |
+
}
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
### Example Request
|
| 44 |
+
|
| 45 |
+
**Request Payload:**
|
| 46 |
+
|
| 47 |
+
```json
|
| 48 |
+
{
|
| 49 |
+
"code": "print('Hello, World!')",
|
| 50 |
+
"timeout": 10,
|
| 51 |
+
"webhook_url": "https://example.com/webhook",
|
| 52 |
+
"id": "unique-request-id"
|
| 53 |
+
}
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
**cURL Command:**
|
| 57 |
+
|
| 58 |
+
```bash
|
| 59 |
+
curl -X POST \
|
| 60 |
+
-H "x-api-key: YOUR_API_KEY" \
|
| 61 |
+
-H "Content-Type: application/json" \
|
| 62 |
+
-d '{"code": "print('Hello, World!')", "timeout": 10, "webhook_url": "https://example.com/webhook", "id": "unique-request-id"}' \
|
| 63 |
+
http://your-api-endpoint/v1/code/execute/python
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
## 4. Response
|
| 67 |
+
|
| 68 |
+
### Success Response
|
| 69 |
+
|
| 70 |
+
The success response follows the general response format defined in `app.py`. Here's an example:
|
| 71 |
+
|
| 72 |
+
```json
|
| 73 |
+
{
|
| 74 |
+
"endpoint": "/v1/code/execute/python",
|
| 75 |
+
"code": 200,
|
| 76 |
+
"id": "unique-request-id",
|
| 77 |
+
"job_id": "generated-job-id",
|
| 78 |
+
"response": {
|
| 79 |
+
"result": null,
|
| 80 |
+
"stdout": "Hello, World!\n",
|
| 81 |
+
"stderr": "",
|
| 82 |
+
"exit_code": 0
|
| 83 |
+
},
|
| 84 |
+
"message": "success",
|
| 85 |
+
"pid": 12345,
|
| 86 |
+
"queue_id": 1234567890,
|
| 87 |
+
"run_time": 0.123,
|
| 88 |
+
"queue_time": 0.0,
|
| 89 |
+
"total_time": 0.123,
|
| 90 |
+
"queue_length": 0,
|
| 91 |
+
"build_number": "1.0.0"
|
| 92 |
+
}
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
### Error Responses
|
| 96 |
+
|
| 97 |
+
#### Missing or Invalid Parameters
|
| 98 |
+
|
| 99 |
+
**Status Code:** 400 Bad Request
|
| 100 |
+
|
| 101 |
+
```json
|
| 102 |
+
{
|
| 103 |
+
"error": "Missing or invalid parameters",
|
| 104 |
+
"stdout": "",
|
| 105 |
+
"exit_code": 400
|
| 106 |
+
}
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
#### Execution Error
|
| 110 |
+
|
| 111 |
+
**Status Code:** 400 Bad Request
|
| 112 |
+
|
| 113 |
+
```json
|
| 114 |
+
{
|
| 115 |
+
"error": "Error message from the executed code",
|
| 116 |
+
"stdout": "Output from the executed code",
|
| 117 |
+
"exit_code": 400
|
| 118 |
+
}
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
#### Execution Timeout
|
| 122 |
+
|
| 123 |
+
**Status Code:** 408 Request Timeout
|
| 124 |
+
|
| 125 |
+
```json
|
| 126 |
+
{
|
| 127 |
+
"error": "Execution timed out after 10 seconds"
|
| 128 |
+
}
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
#### Internal Server Error
|
| 132 |
+
|
| 133 |
+
**Status Code:** 500 Internal Server Error
|
| 134 |
+
|
| 135 |
+
```json
|
| 136 |
+
{
|
| 137 |
+
"error": "An internal server error occurred",
|
| 138 |
+
"stdout": "",
|
| 139 |
+
"stderr": "",
|
| 140 |
+
"exit_code": 500
|
| 141 |
+
}
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
## 5. Error Handling
|
| 145 |
+
|
| 146 |
+
The endpoint handles various types of errors, including:
|
| 147 |
+
|
| 148 |
+
- Missing or invalid parameters (400 Bad Request)
|
| 149 |
+
- Execution errors, such as syntax errors or exceptions (400 Bad Request)
|
| 150 |
+
- Execution timeout (408 Request Timeout)
|
| 151 |
+
- Internal server errors (500 Internal Server Error)
|
| 152 |
+
|
| 153 |
+
The main application context (`app.py`) also includes error handling for queue overload (429 Too Many Requests) and other general errors.
|
| 154 |
+
|
| 155 |
+
## 6. Usage Notes
|
| 156 |
+
|
| 157 |
+
- The executed code runs in a sandboxed environment, with limited access to system resources.
|
| 158 |
+
- The code execution is limited to a maximum of 300 seconds (5 minutes) by default, but this can be adjusted using the `timeout` parameter.
|
| 159 |
+
- The execution result, including stdout, stderr, and the return value, is captured and returned in the response.
|
| 160 |
+
- If a `webhook_url` is provided, the execution result will also be sent to the specified webhook.
|
| 161 |
+
|
| 162 |
+
## 7. Common Issues
|
| 163 |
+
|
| 164 |
+
- Attempting to execute code that accesses restricted resources or performs disallowed operations may result in an execution error.
|
| 165 |
+
- Long-running or resource-intensive code may trigger the execution timeout.
|
| 166 |
+
- Providing an invalid `webhook_url` will prevent the execution result from being delivered to the specified webhook.
|
| 167 |
+
|
| 168 |
+
## 8. Best Practices
|
| 169 |
+
|
| 170 |
+
- Always validate and sanitize user input to prevent code injection attacks.
|
| 171 |
+
- Set an appropriate timeout value based on the expected execution time of the code.
|
| 172 |
+
- Monitor the execution logs for any errors or unexpected behavior.
|
| 173 |
+
- Implement rate limiting or queue management to prevent abuse or overload of the endpoint.
|
| 174 |
+
- Consider implementing additional security measures, such as code sandboxing or whitelisting/blacklisting certain operations or modules.
|
docs/ffmpeg/ffmpeg_compose.md
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# FFmpeg Compose API Endpoint
|
| 2 |
+
|
| 3 |
+
## 1. Overview
|
| 4 |
+
|
| 5 |
+
The `/v1/ffmpeg/compose` endpoint is a flexible and powerful API that allows users to compose complex FFmpeg commands by providing input files, filters, and output options. This endpoint is part of the version 1.0 API structure, as shown in the `app.py` file. It is designed to handle various media processing tasks, such as video and audio manipulation, transcoding, and more.
|
| 6 |
+
|
| 7 |
+
## 2. Endpoint
|
| 8 |
+
|
| 9 |
+
**URL Path:** `/v1/ffmpeg/compose`
|
| 10 |
+
**HTTP Method:** `POST`
|
| 11 |
+
|
| 12 |
+
## 3. Request
|
| 13 |
+
|
| 14 |
+
### Headers
|
| 15 |
+
|
| 16 |
+
- `x-api-key` (required): The API key for authentication.
|
| 17 |
+
|
| 18 |
+
### Body Parameters
|
| 19 |
+
|
| 20 |
+
The request body should be a JSON object with the following properties:
|
| 21 |
+
|
| 22 |
+
- `inputs` (required, array): An array of input file objects, each containing:
|
| 23 |
+
- `file_url` (required, string): The URL of the input file.
|
| 24 |
+
- `options` (optional, array): An array of option objects, each containing:
|
| 25 |
+
- `option` (required, string): The FFmpeg option.
|
| 26 |
+
- `argument` (optional, string, number, or null): The argument for the option.
|
| 27 |
+
- `filters` (optional, array): An array of filter objects, each containing:
|
| 28 |
+
- `filter` (required, string): The FFmpeg filter.
|
| 29 |
+
- `outputs` (required, array): An array of output option objects, each containing:
|
| 30 |
+
- `options` (required, array): An array of option objects, each containing:
|
| 31 |
+
- `option` (required, string): The FFmpeg option.
|
| 32 |
+
- `argument` (optional, string, number, or null): The argument for the option.
|
| 33 |
+
- `global_options` (optional, array): An array of global option objects, each containing:
|
| 34 |
+
- `option` (required, string): The FFmpeg global option.
|
| 35 |
+
- `argument` (optional, string, number, or null): The argument for the option.
|
| 36 |
+
- `metadata` (optional, object): An object specifying which metadata to include in the response, with the following properties:
|
| 37 |
+
- `thumbnail` (optional, boolean): Whether to include a thumbnail for the output file.
|
| 38 |
+
- `filesize` (optional, boolean): Whether to include the file size of the output file.
|
| 39 |
+
- `duration` (optional, boolean): Whether to include the duration of the output file.
|
| 40 |
+
- `bitrate` (optional, boolean): Whether to include the bitrate of the output file.
|
| 41 |
+
- `encoder` (optional, boolean): Whether to include the encoder used for the output file.
|
| 42 |
+
- `webhook_url` (required, string): The URL to send the response webhook.
|
| 43 |
+
- `id` (required, string): A unique identifier for the request.
|
| 44 |
+
|
| 45 |
+
### Example Request
|
| 46 |
+
|
| 47 |
+
```json
|
| 48 |
+
{
|
| 49 |
+
"inputs": [
|
| 50 |
+
{
|
| 51 |
+
"file_url": "https://example.com/video1.mp4",
|
| 52 |
+
"options": [
|
| 53 |
+
{
|
| 54 |
+
"option": "-ss",
|
| 55 |
+
"argument": 10
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"option": "-t",
|
| 59 |
+
"argument": 20
|
| 60 |
+
}
|
| 61 |
+
]
|
| 62 |
+
},
|
| 63 |
+
{
|
| 64 |
+
"file_url": "https://example.com/video2.mp4"
|
| 65 |
+
}
|
| 66 |
+
],
|
| 67 |
+
"filters": [
|
| 68 |
+
{
|
| 69 |
+
"filter": "hflip"
|
| 70 |
+
}
|
| 71 |
+
],
|
| 72 |
+
"outputs": [
|
| 73 |
+
{
|
| 74 |
+
"options": [
|
| 75 |
+
{
|
| 76 |
+
"option": "-c:v",
|
| 77 |
+
"argument": "libx264"
|
| 78 |
+
},
|
| 79 |
+
{
|
| 80 |
+
"option": "-crf",
|
| 81 |
+
"argument": 23
|
| 82 |
+
}
|
| 83 |
+
]
|
| 84 |
+
}
|
| 85 |
+
],
|
| 86 |
+
"global_options": [
|
| 87 |
+
{
|
| 88 |
+
"option": "-y"
|
| 89 |
+
}
|
| 90 |
+
],
|
| 91 |
+
"metadata": {
|
| 92 |
+
"thumbnail": true,
|
| 93 |
+
"filesize": true,
|
| 94 |
+
"duration": true,
|
| 95 |
+
"bitrate": true,
|
| 96 |
+
"encoder": true
|
| 97 |
+
},
|
| 98 |
+
"webhook_url": "https://example.com/webhook",
|
| 99 |
+
"id": "unique-request-id"
|
| 100 |
+
}
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
```bash
|
| 104 |
+
curl -X POST \
|
| 105 |
+
https://api.example.com/v1/ffmpeg/compose \
|
| 106 |
+
-H 'x-api-key: YOUR_API_KEY' \
|
| 107 |
+
-H 'Content-Type: application/json' \
|
| 108 |
+
-d '{
|
| 109 |
+
"inputs": [
|
| 110 |
+
{
|
| 111 |
+
"file_url": "https://example.com/video1.mp4",
|
| 112 |
+
"options": [
|
| 113 |
+
{
|
| 114 |
+
"option": "-ss",
|
| 115 |
+
"argument": 10
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"option": "-t",
|
| 119 |
+
"argument": 20
|
| 120 |
+
}
|
| 121 |
+
]
|
| 122 |
+
},
|
| 123 |
+
{
|
| 124 |
+
"file_url": "https://example.com/video2.mp4"
|
| 125 |
+
}
|
| 126 |
+
],
|
| 127 |
+
"filters": [
|
| 128 |
+
{
|
| 129 |
+
"filter": "hflip"
|
| 130 |
+
}
|
| 131 |
+
],
|
| 132 |
+
"outputs": [
|
| 133 |
+
{
|
| 134 |
+
"options": [
|
| 135 |
+
{
|
| 136 |
+
"option": "-c:v",
|
| 137 |
+
"argument": "libx264"
|
| 138 |
+
},
|
| 139 |
+
{
|
| 140 |
+
"option": "-crf",
|
| 141 |
+
"argument": 23
|
| 142 |
+
}
|
| 143 |
+
]
|
| 144 |
+
}
|
| 145 |
+
],
|
| 146 |
+
"global_options": [
|
| 147 |
+
{
|
| 148 |
+
"option": "-y"
|
| 149 |
+
}
|
| 150 |
+
],
|
| 151 |
+
"metadata": {
|
| 152 |
+
"thumbnail": true,
|
| 153 |
+
"filesize": true,
|
| 154 |
+
"duration": true,
|
| 155 |
+
"bitrate": true,
|
| 156 |
+
"encoder": true
|
| 157 |
+
},
|
| 158 |
+
"webhook_url": "https://example.com/webhook",
|
| 159 |
+
"id": "unique-request-id"
|
| 160 |
+
}'
|
| 161 |
+
```
|
| 162 |
+
|
| 163 |
+
## 4. Response
|
| 164 |
+
|
| 165 |
+
### Success Response
|
| 166 |
+
|
| 167 |
+
The response will be sent to the specified `webhook_url` as a JSON object with the following properties:
|
| 168 |
+
|
| 169 |
+
- `endpoint` (string): The endpoint URL (`/v1/ffmpeg/compose`).
|
| 170 |
+
- `code` (number): The HTTP status code (200 for success).
|
| 171 |
+
- `id` (string): The unique identifier for the request.
|
| 172 |
+
- `job_id` (string): The unique job ID assigned to the request.
|
| 173 |
+
- `response` (array): An array of output file objects, each containing:
|
| 174 |
+
- `file_url` (string): The URL of the uploaded output file.
|
| 175 |
+
- `thumbnail_url` (string, optional): The URL of the uploaded thumbnail, if requested.
|
| 176 |
+
- `filesize` (number, optional): The file size of the output file, if requested.
|
| 177 |
+
- `duration` (number, optional): The duration of the output file, if requested.
|
| 178 |
+
- `bitrate` (number, optional): The bitrate of the output file, if requested.
|
| 179 |
+
- `encoder` (string, optional): The encoder used for the output file, if requested.
|
| 180 |
+
- `message` (string): The success message ("success").
|
| 181 |
+
- `pid` (number): The process ID of the worker that processed the request.
|
| 182 |
+
- `queue_id` (number): The ID of the queue used for processing the request.
|
| 183 |
+
- `run_time` (number): The time taken to process the request (in seconds).
|
| 184 |
+
- `queue_time` (number): The time the request spent in the queue (in seconds).
|
| 185 |
+
- `total_time` (number): The total time taken to process the request, including queue time (in seconds).
|
| 186 |
+
- `queue_length` (number): The current length of the processing queue.
|
| 187 |
+
- `build_number` (string): The build number of the application.
|
| 188 |
+
|
| 189 |
+
### Error Responses
|
| 190 |
+
|
| 191 |
+
- **400 Bad Request**: The request payload is invalid or missing required parameters.
|
| 192 |
+
- **401 Unauthorized**: The provided API key is invalid or missing.
|
| 193 |
+
- **429 Too Many Requests**: The maximum queue length has been reached.
|
| 194 |
+
- **500 Internal Server Error**: An unexpected error occurred while processing the request.
|
| 195 |
+
|
| 196 |
+
Example error response:
|
| 197 |
+
|
| 198 |
+
```json
|
| 199 |
+
{
|
| 200 |
+
"code": 400,
|
| 201 |
+
"id": "unique-request-id",
|
| 202 |
+
"job_id": "job-id",
|
| 203 |
+
"message": "Invalid request payload: 'inputs' is a required property",
|
| 204 |
+
"pid": 123,
|
| 205 |
+
"queue_id": 456,
|
| 206 |
+
"queue_length": 0,
|
| 207 |
+
"build_number": "1.0.0"
|
| 208 |
+
}
|
| 209 |
+
```
|
| 210 |
+
|
| 211 |
+
## 5. Error Handling
|
| 212 |
+
|
| 213 |
+
The API handles various types of errors, including:
|
| 214 |
+
|
| 215 |
+
- **Missing or invalid parameters**: If the request payload is missing required parameters or contains invalid data types, a 400 Bad Request error will be returned.
|
| 216 |
+
- **Authentication failure**: If the provided API key is invalid or missing, a 401 Unauthorized error will be returned.
|
| 217 |
+
- **Queue limit reached**: If the maximum queue length is reached, a 429 Too Many Requests error will be returned.
|
| 218 |
+
- **Unexpected errors**: If an unexpected error occurs during request processing, a 500 Internal Server Error will be returned.
|
| 219 |
+
|
| 220 |
+
The main application context (`app.py`) includes error handling for the processing queue. If the maximum queue length is set and the queue size reaches that limit, new requests will be rejected with a 429 Too Many Requests error.
|
| 221 |
+
|
| 222 |
+
## 6. Usage Notes
|
| 223 |
+
|
| 224 |
+
- The `inputs` array must contain at least one input file object.
|
| 225 |
+
- The `outputs` array must contain at least one output option object.
|
| 226 |
+
- The `filters` array is optional and can be used to apply FFmpeg filters to the input files.
|
| 227 |
+
- The `global_options` array is optional and can be used to specify global FFmpeg options.
|
| 228 |
+
- The `metadata` object is optional and can be used to request specific metadata for the output files.
|
| 229 |
+
- The `webhook_url` parameter is required and specifies the URL where the response should be sent.
|
| 230 |
+
- The `id` parameter is required and should be a unique identifier for the request.
|
| 231 |
+
|
| 232 |
+
## 7. Common Issues
|
| 233 |
+
|
| 234 |
+
- Providing invalid or malformed input file URLs.
|
| 235 |
+
- Specifying invalid or unsupported FFmpeg options or filters.
|
| 236 |
+
- Reaching the maximum queue length, resulting in a 429 Too Many Requests error.
|
| 237 |
+
- Network or connectivity issues that prevent the response webhook from being delivered.
|
| 238 |
+
|
| 239 |
+
## 8. Best Practices
|
| 240 |
+
|
| 241 |
+
- Validate input file URLs and ensure they are accessible before sending the request.
|
| 242 |
+
- Test your FFmpeg command locally before using the API to ensure it works as expected.
|
| 243 |
+
- Monitor the queue length and adjust the maximum queue length as needed to prevent overloading the system.
|
| 244 |
+
- Implement retry mechanisms for handling failed webhook deliveries or other transient errors.
|
| 245 |
+
- Use unique and descriptive `id` values for each request to aid in troubleshooting and monitoring.
|
docs/image/convert/image_to_video.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Image to Video Conversion
|
| 2 |
+
|
| 3 |
+
## 1. Overview
|
| 4 |
+
|
| 5 |
+
The `/v1/image/convert/video` endpoint is part of the Flask API application and is responsible for converting an image into a video file. This endpoint is registered in the `app.py` file under the `v1_image_convert_video_bp` blueprint, which is imported from the `routes.v1.image.convert.image_to_video` module.
|
| 6 |
+
|
| 7 |
+
## 2. Endpoint
|
| 8 |
+
|
| 9 |
+
**URL Path:** `/v1/image/convert/video`
|
| 10 |
+
**HTTP Method:** `POST`
|
| 11 |
+
|
| 12 |
+
## 3. Request
|
| 13 |
+
|
| 14 |
+
### Headers
|
| 15 |
+
|
| 16 |
+
- `x-api-key` (required): The API key for authentication.
|
| 17 |
+
|
| 18 |
+
### Body Parameters
|
| 19 |
+
|
| 20 |
+
The request body must be in JSON format and should include the following parameters:
|
| 21 |
+
|
| 22 |
+
| Parameter | Type | Required | Description |
|
| 23 |
+
|-------------|--------|----------|--------------------------------------------------------------|
|
| 24 |
+
| `image_url` | string | Yes | The URL of the image to be converted into a video. |
|
| 25 |
+
| `length` | number | No | The desired length of the video in seconds (default: 5). |
|
| 26 |
+
| `frame_rate`| integer| No | The frame rate of the output video (default: 30). |
|
| 27 |
+
| `zoom_speed`| number | No | The speed of the zoom effect (0-100, default: 3). |
|
| 28 |
+
| `webhook_url`| string| No | The URL to receive a webhook notification upon completion. |
|
| 29 |
+
| `id` | string | No | An optional identifier for the request. |
|
| 30 |
+
|
| 31 |
+
The `validate_payload` decorator in the `routes.v1.image.convert.image_to_video` module enforces the following JSON schema for the request body:
|
| 32 |
+
|
| 33 |
+
```json
|
| 34 |
+
{
|
| 35 |
+
"type": "object",
|
| 36 |
+
"properties": {
|
| 37 |
+
"image_url": {"type": "string", "format": "uri"},
|
| 38 |
+
"length": {"type": "number", "minimum": 1, "maximum": 60},
|
| 39 |
+
"frame_rate": {"type": "integer", "minimum": 15, "maximum": 60},
|
| 40 |
+
"zoom_speed": {"type": "number", "minimum": 0, "maximum": 100},
|
| 41 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 42 |
+
"id": {"type": "string"}
|
| 43 |
+
},
|
| 44 |
+
"required": ["image_url"],
|
| 45 |
+
"additionalProperties": false
|
| 46 |
+
}
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
### Example Request
|
| 50 |
+
|
| 51 |
+
```json
|
| 52 |
+
{
|
| 53 |
+
"image_url": "https://example.com/image.jpg",
|
| 54 |
+
"length": 10,
|
| 55 |
+
"frame_rate": 24,
|
| 56 |
+
"zoom_speed": 5,
|
| 57 |
+
"webhook_url": "https://example.com/webhook",
|
| 58 |
+
"id": "request-123"
|
| 59 |
+
}
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
```bash
|
| 63 |
+
curl -X POST \
|
| 64 |
+
-H "x-api-key: YOUR_API_KEY" \
|
| 65 |
+
-H "Content-Type: application/json" \
|
| 66 |
+
-d '{"image_url": "https://example.com/image.jpg", "length": 10, "frame_rate": 24, "zoom_speed": 5, "webhook_url": "https://example.com/webhook", "id": "request-123"}' \
|
| 67 |
+
http://your-api-endpoint/v1/image/convert/video
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
## 4. Response
|
| 71 |
+
|
| 72 |
+
### Success Response
|
| 73 |
+
|
| 74 |
+
Upon successful processing, the endpoint returns a JSON response with the following structure:
|
| 75 |
+
|
| 76 |
+
```json
|
| 77 |
+
{
|
| 78 |
+
"code": 200,
|
| 79 |
+
"id": "request-123",
|
| 80 |
+
"job_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
|
| 81 |
+
"response": "https://cloud-storage.example.com/converted-video.mp4",
|
| 82 |
+
"message": "success",
|
| 83 |
+
"run_time": 2.345,
|
| 84 |
+
"queue_time": 0.123,
|
| 85 |
+
"total_time": 2.468,
|
| 86 |
+
"pid": 12345,
|
| 87 |
+
"queue_id": 1234567890,
|
| 88 |
+
"queue_length": 0,
|
| 89 |
+
"build_number": "1.0.0"
|
| 90 |
+
}
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
The `response` field contains the URL of the converted video file uploaded to cloud storage.
|
| 94 |
+
|
| 95 |
+
### Error Responses
|
| 96 |
+
|
| 97 |
+
#### 429 Too Many Requests
|
| 98 |
+
|
| 99 |
+
If the maximum queue length is reached, the endpoint returns a 429 Too Many Requests response:
|
| 100 |
+
|
| 101 |
+
```json
|
| 102 |
+
{
|
| 103 |
+
"code": 429,
|
| 104 |
+
"id": "request-123",
|
| 105 |
+
"job_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
|
| 106 |
+
"message": "MAX_QUEUE_LENGTH (10) reached",
|
| 107 |
+
"pid": 12345,
|
| 108 |
+
"queue_id": 1234567890,
|
| 109 |
+
"queue_length": 10,
|
| 110 |
+
"build_number": "1.0.0"
|
| 111 |
+
}
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
#### 500 Internal Server Error
|
| 115 |
+
|
| 116 |
+
If an exception occurs during the image-to-video conversion process, the endpoint returns a 500 Internal Server Error response:
|
| 117 |
+
|
| 118 |
+
```json
|
| 119 |
+
{
|
| 120 |
+
"code": 500,
|
| 121 |
+
"id": "request-123",
|
| 122 |
+
"job_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
|
| 123 |
+
"message": "Error message describing the exception",
|
| 124 |
+
"pid": 12345,
|
| 125 |
+
"queue_id": 1234567890,
|
| 126 |
+
"queue_length": 0,
|
| 127 |
+
"build_number": "1.0.0"
|
| 128 |
+
}
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
## 5. Error Handling
|
| 132 |
+
|
| 133 |
+
The endpoint handles the following types of errors:
|
| 134 |
+
|
| 135 |
+
- **Missing or invalid parameters**: If the request body is missing required parameters or contains invalid parameter values, the `validate_payload` decorator will return a 400 Bad Request response with a descriptive error message.
|
| 136 |
+
- **Queue length exceeded**: If the maximum queue length is reached and the `bypass_queue` parameter is set to `False`, the endpoint returns a 429 Too Many Requests response.
|
| 137 |
+
- **Exceptions during processing**: If an exception occurs during the image-to-video conversion process, the endpoint returns a 500 Internal Server Error response with the error message.
|
| 138 |
+
|
| 139 |
+
## 6. Usage Notes
|
| 140 |
+
|
| 141 |
+
- The `image_url` parameter must be a valid URL pointing to an image file.
|
| 142 |
+
- The `length` parameter specifies the duration of the output video in seconds and must be between 1 and 60.
|
| 143 |
+
- The `frame_rate` parameter specifies the frame rate of the output video and must be between 15 and 60.
|
| 144 |
+
- The `zoom_speed` parameter controls the speed of the zoom effect and must be between 0 and 100.
|
| 145 |
+
- The `webhook_url` parameter is optional and can be used to receive a notification when the conversion is complete.
|
| 146 |
+
- The `id` parameter is optional and can be used to identify the request.
|
| 147 |
+
|
| 148 |
+
## 7. Common Issues
|
| 149 |
+
|
| 150 |
+
- Providing an invalid or inaccessible `image_url` will result in an error during processing.
|
| 151 |
+
- Specifying invalid parameter values outside the allowed ranges will result in a 400 Bad Request response.
|
| 152 |
+
- If the maximum queue length is reached and the `bypass_queue` parameter is set to `False`, the request will be rejected with a 429 Too Many Requests response.
|
| 153 |
+
|
| 154 |
+
## 8. Best Practices
|
| 155 |
+
|
| 156 |
+
- Validate the `image_url` parameter before sending the request to ensure it points to a valid and accessible image file.
|
| 157 |
+
- Use the `webhook_url` parameter to receive notifications about the completion of the conversion process, rather than polling the API repeatedly.
|
| 158 |
+
- Provide the `id` parameter to easily identify and track the request in logs or notifications.
|
| 159 |
+
- Consider setting the `bypass_queue` parameter to `True` for time-sensitive requests to bypass the queue and process the request immediately.
|
docs/image/screenshot_webpage.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Playwright Screenshot Endpoint
|
| 2 |
+
|
| 3 |
+
**Implemented by:** [Harrison Fisher](https://github.com/HarrisonFisher)
|
| 4 |
+
|
| 5 |
+
## ⚠️ Disclaimer
|
| 6 |
+
- This endpoint is **not intended to bypass CAPTCHAs, Cloudflare**, or other anti-bot protections.
|
| 7 |
+
- Please **do not create issues or requests** asking for CAPTCHA or Cloudflare bypass features.
|
| 8 |
+
- It will not work for sites with such defenses, and attempting to automate against protected sites could result in your IP address being blacklisted by Cloudflare or similar services.
|
| 9 |
+
- The screenshot endpoint supports custom HTML, JavaScript, and CSS input for flexible rendering and automation, as an alternative to using a URL.
|
| 10 |
+
- This feature should only be used on sites you own or have explicit permission to automate.
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
## 1. Overview
|
| 14 |
+
|
| 15 |
+
The `/v1/image/screenshot/webpage` endpoint allows you to capture screenshots of web pages using the Playwright browser automation library. It supports advanced options such as viewport size, device emulation, cookies, headers, element targeting, and more. Screenshots are uploaded to cloud storage, and the resulting URL is returned. This endpoint is part of the v1 API suite and is registered in the main Flask application as a blueprint.
|
| 16 |
+
|
| 17 |
+
## 2. Endpoint
|
| 18 |
+
|
| 19 |
+
- **URL Path:** `/v1/image/screenshot/webpage`
|
| 20 |
+
- **HTTP Method:** `POST`
|
| 21 |
+
|
| 22 |
+
## 3. Request
|
| 23 |
+
|
| 24 |
+
### Headers
|
| 25 |
+
|
| 26 |
+
- `x-api-key` (required): Your API key for authentication.
|
| 27 |
+
- `Content-Type`: `application/json`
|
| 28 |
+
|
| 29 |
+
### Body Parameters
|
| 30 |
+
|
| 31 |
+
The request body must be a JSON object with the following properties:
|
| 32 |
+
|
| 33 |
+
- `url` (string, required): The URL of the web page to capture.
|
| 34 |
+
- `html` (string, optional): Raw HTML content to render and capture.
|
| 35 |
+
**Note:** Either `url` or `html` must be provided, but not both.
|
| 36 |
+
- `viewport_width` (integer, optional): Viewport width in pixels.
|
| 37 |
+
- `viewport_height` (integer, optional): Viewport height in pixels.
|
| 38 |
+
- `full_page` (boolean, optional): Capture the full scrollable page. Default: `false`.
|
| 39 |
+
- `format` (string, optional): Image format, either `png` or `jpeg`. Default: `png`.
|
| 40 |
+
- `delay` (integer, optional): Delay in milliseconds before taking the screenshot.
|
| 41 |
+
- `device_scale_factor` (number, optional): Device scale factor (e.g., 2 for retina).
|
| 42 |
+
- `user_agent` (string, optional): Custom user agent string.
|
| 43 |
+
- `cookies` (array, optional): List of cookies to set. Each cookie is an object with `name`, `value`, and `domain`.
|
| 44 |
+
- `headers` (object, optional): Additional HTTP headers to set.
|
| 45 |
+
- `quality` (integer, optional): JPEG quality (0-100, only for `jpeg` format).
|
| 46 |
+
- `clip` (object, optional): Region to capture, with `x`, `y`, `width`, `height` (all numbers).
|
| 47 |
+
- `timeout` (integer, optional): Navigation timeout in milliseconds. Minimum: 100.
|
| 48 |
+
- `wait_until` (string, optional): When to consider navigation succeeded. One of `load`, `domcontentloaded`, `networkidle`, `networkidle2`. Default: `load`.
|
| 49 |
+
- `wait_for_selector` (string, optional): Wait for a selector before screenshot.
|
| 50 |
+
- `emulate` (object, optional): Emulation options, e.g., `{ "color_scheme": "dark" }`.
|
| 51 |
+
- `omit_background` (boolean, optional): Hide default white background. Default: `false`.
|
| 52 |
+
- `selector` (string, optional): CSS selector for a specific element to screenshot.
|
| 53 |
+
- `webhook_url` (string, optional): If provided, results are sent to this URL asynchronously.
|
| 54 |
+
- `id` (string, optional): Custom identifier for the request.
|
| 55 |
+
- `js` (string, optional): JavaScript code to inject into the page before taking the screenshot.
|
| 56 |
+
- `css` (string, optional): CSS code to inject into the page before taking the screenshot.
|
| 57 |
+
|
| 58 |
+
#### Example Request
|
| 59 |
+
|
| 60 |
+
```json
|
| 61 |
+
{
|
| 62 |
+
"url": "https://example.com",
|
| 63 |
+
"viewport_width": 1280,
|
| 64 |
+
"viewport_height": 720,
|
| 65 |
+
"js": "document.body.style.background = 'red';",
|
| 66 |
+
"css": "body { font-size: 30px; }",
|
| 67 |
+
"full_page": true,
|
| 68 |
+
"format": "png",
|
| 69 |
+
"delay": 500,
|
| 70 |
+
"device_scale_factor": 2,
|
| 71 |
+
"user_agent": "CustomAgent/1.0",
|
| 72 |
+
"cookies": [
|
| 73 |
+
{
|
| 74 |
+
"name": "test_cookie",
|
| 75 |
+
"value": "test_value",
|
| 76 |
+
"domain": "example.com",
|
| 77 |
+
"path": "/"
|
| 78 |
+
}
|
| 79 |
+
],
|
| 80 |
+
"headers": {
|
| 81 |
+
"Accept-Language": "en-US,en;q=0.9"
|
| 82 |
+
},
|
| 83 |
+
"quality": 90,
|
| 84 |
+
"clip": {
|
| 85 |
+
"x": 0,
|
| 86 |
+
"y": 0,
|
| 87 |
+
"width": 800,
|
| 88 |
+
"height": 600
|
| 89 |
+
},
|
| 90 |
+
"timeout": 10000,
|
| 91 |
+
"wait_until": "networkidle",
|
| 92 |
+
"wait_for_selector": "#main-content",
|
| 93 |
+
"selector": "#main-content",
|
| 94 |
+
"emulate": {
|
| 95 |
+
"color_scheme": "dark"
|
| 96 |
+
},
|
| 97 |
+
"omit_background": true,
|
| 98 |
+
"webhook_url": "https://your-webhook.com/callback",
|
| 99 |
+
"id": "custom-job-123"
|
| 100 |
+
}
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
**cURL Example:**
|
| 104 |
+
|
| 105 |
+
```bash
|
| 106 |
+
curl -X POST \
|
| 107 |
+
-H "x-api-key: YOUR_API_KEY" \
|
| 108 |
+
-H "Content-Type: application/json" \
|
| 109 |
+
-d '{
|
| 110 |
+
"url": "https://example.com",
|
| 111 |
+
"viewport_width": 1280,
|
| 112 |
+
"viewport_height": 720,
|
| 113 |
+
"js": "document.body.style.background = '\''red'\'';",
|
| 114 |
+
"css": "body { font-size: 30px; }",
|
| 115 |
+
"full_page": true,
|
| 116 |
+
"format": "png",
|
| 117 |
+
"delay": 500,
|
| 118 |
+
"device_scale_factor": 2,
|
| 119 |
+
"user_agent": "CustomAgent/1.0",
|
| 120 |
+
"cookies": [
|
| 121 |
+
{"name": "test_cookie", "value": "test_value", "domain": "example.com", "path": "/"}
|
| 122 |
+
],
|
| 123 |
+
"headers": {"Accept-Language": "en-US,en;q=0.9"},
|
| 124 |
+
"quality": 90,
|
| 125 |
+
"clip": {"x": 0, "y": 0, "width": 800, "height": 600},
|
| 126 |
+
"timeout": 10000,
|
| 127 |
+
"wait_until": "networkidle",
|
| 128 |
+
"wait_for_selector": "#main-content",
|
| 129 |
+
"selector": "#main-content",
|
| 130 |
+
"emulate": {"color_scheme": "dark"},
|
| 131 |
+
"omit_background": true,
|
| 132 |
+
"webhook_url": "https://your-webhook.com/callback",
|
| 133 |
+
"id": "custom-job-123"
|
| 134 |
+
}' \
|
| 135 |
+
https://your-api-endpoint.com/v1/image/screenshot/webpage
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
## 4. Response
|
| 139 |
+
|
| 140 |
+
### Success Response
|
| 141 |
+
|
| 142 |
+
The response is a JSON object containing the cloud storage URL of the screenshot and job metadata. If a `webhook_url` is provided, the result is sent asynchronously to the webhook.
|
| 143 |
+
|
| 144 |
+
```json
|
| 145 |
+
{
|
| 146 |
+
"endpoint": "/v1/image/screenshot/webpage",
|
| 147 |
+
"code": 200,
|
| 148 |
+
"id": "custom-job-123",
|
| 149 |
+
"job_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
|
| 150 |
+
"response": "https://cloud.example.com/screenshot.png",
|
| 151 |
+
"message": "success",
|
| 152 |
+
"pid": 12345,
|
| 153 |
+
"queue_id": 140682639937472,
|
| 154 |
+
"run_time": 2.345,
|
| 155 |
+
"queue_time": 0.012,
|
| 156 |
+
"total_time": 2.357,
|
| 157 |
+
"queue_length": 0,
|
| 158 |
+
"build_number": "1.0.0"
|
| 159 |
+
}
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
### Error Responses
|
| 163 |
+
|
| 164 |
+
- **400 Bad Request**: Invalid or missing parameters.
|
| 165 |
+
- **401 Unauthorized**: Invalid or missing API key.
|
| 166 |
+
- **429 Too Many Requests**: Queue is full.
|
| 167 |
+
- **500 Internal Server Error**: An error occurred during processing.
|
| 168 |
+
|
| 169 |
+
Example error response:
|
| 170 |
+
|
| 171 |
+
```json
|
| 172 |
+
{
|
| 173 |
+
"endpoint": "/v1/image/screenshot/webpage",
|
| 174 |
+
"code": 500,
|
| 175 |
+
"id": "custom-job-123",
|
| 176 |
+
"job_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
|
| 177 |
+
"response": null,
|
| 178 |
+
"message": "Error message details",
|
| 179 |
+
"pid": 12345,
|
| 180 |
+
"queue_id": 140682639937472,
|
| 181 |
+
"run_time": 0.123,
|
| 182 |
+
"queue_time": 0.056,
|
| 183 |
+
"total_time": 0.179,
|
| 184 |
+
"queue_length": 1,
|
| 185 |
+
"build_number": "1.0.0"
|
| 186 |
+
}
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
## 5. Error Handling
|
| 190 |
+
|
| 191 |
+
- **Missing or invalid parameters**: Returns 400 with details.
|
| 192 |
+
- **Authentication failure**: Returns 401.
|
| 193 |
+
- **Queue full**: Returns 429.
|
| 194 |
+
- **Processing error**: Returns 500 with error message.
|
| 195 |
+
|
| 196 |
+
## 6. Usage Notes
|
| 197 |
+
|
| 198 |
+
- If `webhook_url` is provided, the request is processed asynchronously and the result is sent to the webhook.
|
| 199 |
+
- Screenshots are always uploaded to cloud storage; the response contains the file URL.
|
| 200 |
+
- Use `selector` to capture a specific element instead of the full page.
|
| 201 |
+
- The `clip` parameter allows capturing a specific region.
|
| 202 |
+
- The endpoint enforces strict payload validation.
|
| 203 |
+
|
| 204 |
+
## 7. Common Issues
|
| 205 |
+
|
| 206 |
+
- Invalid or inaccessible URL.
|
| 207 |
+
- Selector not found (if using `wait_for_selector` or `selector`).
|
| 208 |
+
- Cookie domain mismatch.
|
| 209 |
+
- Timeout errors for slow-loading pages.
|
| 210 |
+
- Invalid API key.
|
| 211 |
+
|
| 212 |
+
## 8. Best Practices
|
| 213 |
+
|
| 214 |
+
- Always validate your input parameters before sending the request.
|
| 215 |
+
- Use unique `id` values for tracking jobs.
|
| 216 |
+
- Monitor queue length and handle 429 errors gracefully.
|
| 217 |
+
- Use HTTPS for all URLs and webhooks.
|
| 218 |
+
- Test your selectors and page state locally before automating screenshots.
|
docs/media/convert/media_convert.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Media Convert Endpoint Documentation
|
| 2 |
+
|
| 3 |
+
## 1. Overview
|
| 4 |
+
|
| 5 |
+
The `/v1/media/convert` endpoint is part of the Flask API application and is responsible for converting media files (audio or video) from one format to another. This endpoint fits into the overall API structure as a part of the `v1` blueprint, which contains various media-related functionalities.
|
| 6 |
+
|
| 7 |
+
## 2. Endpoint
|
| 8 |
+
|
| 9 |
+
**URL Path:** `/v1/media/convert`
|
| 10 |
+
**HTTP Method:** `POST`
|
| 11 |
+
|
| 12 |
+
## 3. Request
|
| 13 |
+
|
| 14 |
+
### Headers
|
| 15 |
+
|
| 16 |
+
- `x-api-key` (required): The API key for authentication.
|
| 17 |
+
|
| 18 |
+
### Body Parameters
|
| 19 |
+
|
| 20 |
+
The request body must be a JSON object with the following properties:
|
| 21 |
+
|
| 22 |
+
- `media_url` (required, string): The URL of the media file to be converted.
|
| 23 |
+
- `format` (required, string): The desired output format for the converted media file.
|
| 24 |
+
- `video_codec` (optional, string): The video codec to be used for the conversion. Default is `libx264`.
|
| 25 |
+
- `video_preset` (optional, string): The video preset to be used for the conversion. Default is `medium`.
|
| 26 |
+
- `video_crf` (optional, number): The Constant Rate Factor (CRF) value for video encoding. Must be between 0 and 51. Default is 23.
|
| 27 |
+
- `audio_codec` (optional, string): The audio codec to be used for the conversion. Default is `aac`.
|
| 28 |
+
- `audio_bitrate` (optional, string): The audio bitrate to be used for the conversion. Default is `128k`.
|
| 29 |
+
- `webhook_url` (optional, string): The URL to receive a webhook notification upon completion of the conversion process.
|
| 30 |
+
- `id` (optional, string): An optional identifier for the conversion request.
|
| 31 |
+
|
| 32 |
+
### Example Request
|
| 33 |
+
|
| 34 |
+
```json
|
| 35 |
+
{
|
| 36 |
+
"media_url": "https://example.com/video.mp4",
|
| 37 |
+
"format": "avi",
|
| 38 |
+
"video_codec": "libx264",
|
| 39 |
+
"video_preset": "medium",
|
| 40 |
+
"video_crf": 23,
|
| 41 |
+
"audio_codec": "aac",
|
| 42 |
+
"audio_bitrate": "128k",
|
| 43 |
+
"webhook_url": "https://example.com/webhook",
|
| 44 |
+
"id": "unique-request-id"
|
| 45 |
+
}
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
```bash
|
| 49 |
+
curl -X POST \
|
| 50 |
+
https://api.example.com/v1/media/convert \
|
| 51 |
+
-H 'x-api-key: YOUR_API_KEY' \
|
| 52 |
+
-H 'Content-Type: application/json' \
|
| 53 |
+
-d '{
|
| 54 |
+
"media_url": "https://example.com/video.mp4",
|
| 55 |
+
"format": "avi",
|
| 56 |
+
"video_codec": "libx264",
|
| 57 |
+
"video_preset": "medium",
|
| 58 |
+
"video_crf": 23,
|
| 59 |
+
"audio_codec": "aac",
|
| 60 |
+
"audio_bitrate": "128k",
|
| 61 |
+
"webhook_url": "https://example.com/webhook",
|
| 62 |
+
"id": "unique-request-id"
|
| 63 |
+
}'
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
## 4. Response
|
| 67 |
+
|
| 68 |
+
### Success Response
|
| 69 |
+
|
| 70 |
+
The success response will be a JSON object containing the URL of the converted media file uploaded to cloud storage, the endpoint path, and a status code of 200.
|
| 71 |
+
|
| 72 |
+
```json
|
| 73 |
+
{
|
| 74 |
+
"code": 200,
|
| 75 |
+
"id": "unique-request-id",
|
| 76 |
+
"job_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
|
| 77 |
+
"response": "https://cloud.example.com/converted-video.avi",
|
| 78 |
+
"message": "success",
|
| 79 |
+
"pid": 12345,
|
| 80 |
+
"queue_id": 1234567890,
|
| 81 |
+
"run_time": 10.234,
|
| 82 |
+
"queue_time": 0.123,
|
| 83 |
+
"total_time": 10.357,
|
| 84 |
+
"queue_length": 0,
|
| 85 |
+
"build_number": "1.0.0"
|
| 86 |
+
}
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
### Error Responses
|
| 90 |
+
|
| 91 |
+
- **400 Bad Request**: Returned when the request payload is missing or invalid.
|
| 92 |
+
- **401 Unauthorized**: Returned when the `x-api-key` header is missing or invalid.
|
| 93 |
+
- **500 Internal Server Error**: Returned when an unexpected error occurs during the conversion process.
|
| 94 |
+
|
| 95 |
+
Example error response:
|
| 96 |
+
|
| 97 |
+
```json
|
| 98 |
+
{
|
| 99 |
+
"code": 400,
|
| 100 |
+
"id": "unique-request-id",
|
| 101 |
+
"job_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
|
| 102 |
+
"message": "Invalid request payload",
|
| 103 |
+
"pid": 12345,
|
| 104 |
+
"queue_id": 1234567890,
|
| 105 |
+
"queue_length": 0,
|
| 106 |
+
"build_number": "1.0.0"
|
| 107 |
+
}
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
## 5. Error Handling
|
| 111 |
+
|
| 112 |
+
The endpoint uses the `validate_payload` decorator to validate the request payload against a JSON schema. If the payload is missing or invalid, a 400 Bad Request error is returned.
|
| 113 |
+
|
| 114 |
+
The `authenticate` decorator is used to ensure that the request includes a valid `x-api-key` header. If the header is missing or invalid, a 401 Unauthorized error is returned.
|
| 115 |
+
|
| 116 |
+
If an unexpected error occurs during the conversion process, a 500 Internal Server Error is returned, and the error is logged.
|
| 117 |
+
|
| 118 |
+
## 6. Usage Notes
|
| 119 |
+
|
| 120 |
+
- The `media_url` parameter must be a valid URL pointing to the media file to be converted.
|
| 121 |
+
- The `format` parameter must be a valid media format supported by the conversion process.
|
| 122 |
+
- The optional parameters (`video_codec`, `video_preset`, `video_crf`, `audio_codec`, `audio_bitrate`) allow you to customize the conversion settings.
|
| 123 |
+
- If the `webhook_url` parameter is provided, a webhook notification will be sent to the specified URL upon completion of the conversion process.
|
| 124 |
+
- The `id` parameter is optional and can be used to identify the conversion request.
|
| 125 |
+
|
| 126 |
+
## 7. Common Issues
|
| 127 |
+
|
| 128 |
+
- Providing an invalid or inaccessible `media_url`.
|
| 129 |
+
- Specifying an unsupported `format`.
|
| 130 |
+
- Providing invalid values for the optional parameters (e.g., `video_crf` outside the valid range).
|
| 131 |
+
|
| 132 |
+
## 8. Best Practices
|
| 133 |
+
|
| 134 |
+
- Always validate the input parameters on the client-side before sending the request.
|
| 135 |
+
- Use the `id` parameter to track and identify conversion requests.
|
| 136 |
+
- Provide a `webhook_url` to receive notifications about the conversion process completion.
|
| 137 |
+
- Monitor the API logs for any errors or issues during the conversion process.
|
| 138 |
+
- Consider implementing rate limiting or queue management to handle high volumes of requests.
|
docs/media/convert/media_to_mp3.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Media to MP3 Conversion
|
| 2 |
+
|
| 3 |
+
The `/v1/media/convert/mp3` endpoint is part of the Flask API application and is responsible for converting various media files into MP3 format. This endpoint is registered in the `app.py` file under the `v1_media_convert_mp3_bp` blueprint.
|
| 4 |
+
|
| 5 |
+
## Endpoint Details
|
| 6 |
+
|
| 7 |
+
**URL Path:** `/v1/media/convert/mp3`
|
| 8 |
+
|
| 9 |
+
## 1. Overview
|
| 10 |
+
|
| 11 |
+
The `/v1/media/convert/mp3` endpoint is a part of the API's media transformation functionality. It allows users to convert various media files (audio or video) to MP3 format. This endpoint fits into the overall API structure as a part of the `v1` namespace, which represents the first version of the API.
|
| 12 |
+
|
| 13 |
+
## 2. Endpoint
|
| 14 |
+
|
| 15 |
+
```
|
| 16 |
+
POST /v1/media/convert/mp3
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
## 3. Request
|
| 20 |
+
|
| 21 |
+
### Headers
|
| 22 |
+
|
| 23 |
+
- `x-api-key` (required): The API key for authentication.
|
| 24 |
+
|
| 25 |
+
### Body Parameters
|
| 26 |
+
|
| 27 |
+
- `media_url` (required, string): The URL of the media file to be converted.
|
| 28 |
+
- `webhook_url` (optional, string): The URL to receive a webhook notification upon completion.
|
| 29 |
+
- `id` (optional, string): A unique identifier for the request.
|
| 30 |
+
- `bitrate` (optional, string): The desired bitrate for the output MP3 file, in the format `<value>k` (e.g., `128k`). If not provided, defaults to `128k`.
|
| 31 |
+
|
| 32 |
+
The `validate_payload` directive in the routes file enforces the following JSON schema for the request body:
|
| 33 |
+
|
| 34 |
+
```json
|
| 35 |
+
{
|
| 36 |
+
"type": "object",
|
| 37 |
+
"properties": {
|
| 38 |
+
"media_url": {"type": "string", "format": "uri"},
|
| 39 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 40 |
+
"id": {"type": "string"},
|
| 41 |
+
"bitrate": {"type": "string", "pattern": "^[0-9]+k$"}
|
| 42 |
+
},
|
| 43 |
+
"required": ["media_url"],
|
| 44 |
+
"additionalProperties": False
|
| 45 |
+
}
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
### Example Request
|
| 49 |
+
|
| 50 |
+
```json
|
| 51 |
+
{
|
| 52 |
+
"media_url": "https://example.com/video.mp4",
|
| 53 |
+
"webhook_url": "https://example.com/webhook",
|
| 54 |
+
"id": "unique-request-id",
|
| 55 |
+
"bitrate": "192k"
|
| 56 |
+
}
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
```bash
|
| 60 |
+
curl -X POST \
|
| 61 |
+
-H "x-api-key: YOUR_API_KEY" \
|
| 62 |
+
-H "Content-Type: application/json" \
|
| 63 |
+
-d '{"media_url": "https://example.com/video.mp4", "webhook_url": "https://example.com/webhook", "id": "unique-request-id", "bitrate": "192k"}' \
|
| 64 |
+
https://your-api-endpoint.com/v1/media/convert/mp3
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
## 4. Response
|
| 68 |
+
|
| 69 |
+
### Success Response
|
| 70 |
+
|
| 71 |
+
The success response follows the general response structure defined in `app.py`. Here's an example:
|
| 72 |
+
|
| 73 |
+
```json
|
| 74 |
+
{
|
| 75 |
+
"endpoint": "/v1/media/convert/mp3",
|
| 76 |
+
"code": 200,
|
| 77 |
+
"id": "unique-request-id",
|
| 78 |
+
"job_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
|
| 79 |
+
"response": "https://cloud-storage.example.com/converted-file.mp3",
|
| 80 |
+
"message": "success",
|
| 81 |
+
"pid": 12345,
|
| 82 |
+
"queue_id": 6789,
|
| 83 |
+
"run_time": 5.234,
|
| 84 |
+
"queue_time": 0.123,
|
| 85 |
+
"total_time": 5.357,
|
| 86 |
+
"queue_length": 0,
|
| 87 |
+
"build_number": "1.0.0"
|
| 88 |
+
}
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
### Error Responses
|
| 92 |
+
|
| 93 |
+
- **400 Bad Request**: Returned when the request payload is invalid or missing required parameters.
|
| 94 |
+
- **401 Unauthorized**: Returned when the `x-api-key` header is missing or invalid.
|
| 95 |
+
- **500 Internal Server Error**: Returned when an unexpected error occurs during the conversion process.
|
| 96 |
+
|
| 97 |
+
Example error response:
|
| 98 |
+
|
| 99 |
+
```json
|
| 100 |
+
{
|
| 101 |
+
"code": 400,
|
| 102 |
+
"id": "unique-request-id",
|
| 103 |
+
"job_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
|
| 104 |
+
"message": "Invalid request payload: 'media_url' is a required property",
|
| 105 |
+
"pid": 12345,
|
| 106 |
+
"queue_id": 6789,
|
| 107 |
+
"queue_length": 0,
|
| 108 |
+
"build_number": "1.0.0"
|
| 109 |
+
}
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
## 5. Error Handling
|
| 113 |
+
|
| 114 |
+
The endpoint handles the following common errors:
|
| 115 |
+
|
| 116 |
+
- Missing or invalid `media_url` parameter: Returns a 400 Bad Request error.
|
| 117 |
+
- Invalid `bitrate` parameter: Returns a 400 Bad Request error.
|
| 118 |
+
- Authentication failure: Returns a 401 Unauthorized error.
|
| 119 |
+
- Unexpected exceptions during the conversion process: Returns a 500 Internal Server Error.
|
| 120 |
+
|
| 121 |
+
Additionally, the main application context (`app.py`) includes error handling for queue overload. If the maximum queue length is reached, the endpoint will return a 429 Too Many Requests error.
|
| 122 |
+
|
| 123 |
+
## 6. Usage Notes
|
| 124 |
+
|
| 125 |
+
- The `media_url` parameter should point to a valid media file (audio or video) that can be converted to MP3 format.
|
| 126 |
+
- If the `webhook_url` parameter is provided, a webhook notification will be sent to the specified URL upon completion of the conversion process.
|
| 127 |
+
- The `id` parameter can be used to uniquely identify the request, which can be helpful for tracking and logging purposes.
|
| 128 |
+
- The `bitrate` parameter allows you to specify the desired bitrate for the output MP3 file. If not provided, the default bitrate of 128k will be used.
|
| 129 |
+
|
| 130 |
+
## 7. Common Issues
|
| 131 |
+
|
| 132 |
+
- Providing an invalid or inaccessible `media_url`.
|
| 133 |
+
- Attempting to convert unsupported media formats.
|
| 134 |
+
- Exceeding the maximum queue length, resulting in a 429 Too Many Requests error.
|
| 135 |
+
|
| 136 |
+
## 8. Best Practices
|
| 137 |
+
|
| 138 |
+
- Validate the `media_url` parameter before sending the request to ensure it points to a valid and accessible media file.
|
| 139 |
+
- Consider providing a `webhook_url` parameter to receive notifications about the conversion process completion.
|
| 140 |
+
- Use a unique `id` parameter for each request to facilitate tracking and logging.
|
| 141 |
+
- Implement retry mechanisms in case of transient errors or queue overload situations.
|
| 142 |
+
- Monitor the API logs for any errors or issues during the conversion process.
|
docs/media/cut.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Media Cut Endpoint
|
| 2 |
+
|
| 3 |
+
## 1. Overview
|
| 4 |
+
|
| 5 |
+
The `/v1/media/cut` endpoint is part of the Flask API application and is designed to cut specified segments from a media file (video or audio) with optional encoding settings. This endpoint fits into the overall API structure as a part of the `v1` blueprint, which contains various media-related functionalities.
|
| 6 |
+
|
| 7 |
+
## 2. Endpoint
|
| 8 |
+
|
| 9 |
+
**URL Path:** `/v1/media/cut`
|
| 10 |
+
**HTTP Method:** `POST`
|
| 11 |
+
|
| 12 |
+
## 3. Request
|
| 13 |
+
|
| 14 |
+
### Headers
|
| 15 |
+
|
| 16 |
+
- `x-api-key` (required): The API key for authentication.
|
| 17 |
+
|
| 18 |
+
### Body Parameters
|
| 19 |
+
|
| 20 |
+
The request body must be a JSON object with the following properties:
|
| 21 |
+
|
| 22 |
+
- `media_url` (required, string): The URL of the media file to be cut.
|
| 23 |
+
- `cuts` (required, array of objects): An array of cut segments, where each object has the following properties:
|
| 24 |
+
- `start` (required, string): The start time of the cut segment in the format `hh:mm:ss.ms`.
|
| 25 |
+
- `end` (required, string): The end time of the cut segment in the format `hh:mm:ss.ms`.
|
| 26 |
+
- `video_codec` (optional, string): The video codec to be used for encoding the output file. Default is `libx264`.
|
| 27 |
+
- `video_preset` (optional, string): The video preset to be used for encoding the output file. Default is `medium`.
|
| 28 |
+
- `video_crf` (optional, number): The Constant Rate Factor (CRF) value for video encoding. Must be between 0 and 51. Default is 23.
|
| 29 |
+
- `audio_codec` (optional, string): The audio codec to be used for encoding the output file. Default is `aac`.
|
| 30 |
+
- `audio_bitrate` (optional, string): The audio bitrate to be used for encoding the output file. Default is `128k`.
|
| 31 |
+
- `webhook_url` (optional, string): The URL to receive a webhook notification upon completion of the task.
|
| 32 |
+
- `id` (optional, string): A unique identifier for the request.
|
| 33 |
+
|
| 34 |
+
### Example Request
|
| 35 |
+
|
| 36 |
+
```json
|
| 37 |
+
{
|
| 38 |
+
"media_url": "https://example.com/video.mp4",
|
| 39 |
+
"cuts": [
|
| 40 |
+
{
|
| 41 |
+
"start": "00:00:10.000",
|
| 42 |
+
"end": "00:00:20.000"
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"start": "00:00:30.000",
|
| 46 |
+
"end": "00:00:40.000"
|
| 47 |
+
}
|
| 48 |
+
],
|
| 49 |
+
"video_codec": "libx264",
|
| 50 |
+
"video_preset": "medium",
|
| 51 |
+
"video_crf": 23,
|
| 52 |
+
"audio_codec": "aac",
|
| 53 |
+
"audio_bitrate": "128k",
|
| 54 |
+
"webhook_url": "https://example.com/webhook",
|
| 55 |
+
"id": "unique-request-id"
|
| 56 |
+
}
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
```
|
| 60 |
+
curl -X POST \
|
| 61 |
+
https://api.example.com/v1/media/cut \
|
| 62 |
+
-H 'x-api-key: YOUR_API_KEY' \
|
| 63 |
+
-H 'Content-Type: application/json' \
|
| 64 |
+
-d '{
|
| 65 |
+
"media_url": "https://example.com/video.mp4",
|
| 66 |
+
"cuts": [
|
| 67 |
+
{
|
| 68 |
+
"start": "00:00:10.000",
|
| 69 |
+
"end": "00:00:20.000"
|
| 70 |
+
},
|
| 71 |
+
{
|
| 72 |
+
"start": "00:00:30.000",
|
| 73 |
+
"end": "00:00:40.000"
|
| 74 |
+
}
|
| 75 |
+
],
|
| 76 |
+
"video_codec": "libx264",
|
| 77 |
+
"video_preset": "medium",
|
| 78 |
+
"video_crf": 23,
|
| 79 |
+
"audio_codec": "aac",
|
| 80 |
+
"audio_bitrate": "128k",
|
| 81 |
+
"webhook_url": "https://example.com/webhook",
|
| 82 |
+
"id": "unique-request-id"
|
| 83 |
+
}'
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
## 4. Response
|
| 87 |
+
|
| 88 |
+
### Success Response
|
| 89 |
+
|
| 90 |
+
The success response follows the general response structure defined in the `app.py` file. Here's an example:
|
| 91 |
+
|
| 92 |
+
```json
|
| 93 |
+
{
|
| 94 |
+
"code": 200,
|
| 95 |
+
"id": "unique-request-id",
|
| 96 |
+
"job_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
|
| 97 |
+
"response": {
|
| 98 |
+
"file_url": "https://example.com/output.mp4"
|
| 99 |
+
},
|
| 100 |
+
"message": "success",
|
| 101 |
+
"run_time": 5.234,
|
| 102 |
+
"queue_time": 0.012,
|
| 103 |
+
"total_time": 5.246,
|
| 104 |
+
"pid": 12345,
|
| 105 |
+
"queue_id": 1234567890,
|
| 106 |
+
"queue_length": 0,
|
| 107 |
+
"build_number": "1.0.0"
|
| 108 |
+
}
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
### Error Responses
|
| 112 |
+
|
| 113 |
+
- **400 Bad Request**: Returned when the request payload is missing or invalid.
|
| 114 |
+
|
| 115 |
+
```json
|
| 116 |
+
{
|
| 117 |
+
"code": 400,
|
| 118 |
+
"message": "Invalid request payload"
|
| 119 |
+
}
|
| 120 |
+
```
|
| 121 |
+
|
| 122 |
+
- **401 Unauthorized**: Returned when the `x-api-key` header is missing or invalid.
|
| 123 |
+
|
| 124 |
+
```json
|
| 125 |
+
{
|
| 126 |
+
"code": 401,
|
| 127 |
+
"message": "Unauthorized"
|
| 128 |
+
}
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
- **500 Internal Server Error**: Returned when an unexpected error occurs on the server.
|
| 132 |
+
|
| 133 |
+
```json
|
| 134 |
+
{
|
| 135 |
+
"code": 500,
|
| 136 |
+
"message": "Internal Server Error"
|
| 137 |
+
}
|
| 138 |
+
```
|
| 139 |
+
|
| 140 |
+
## 5. Error Handling
|
| 141 |
+
|
| 142 |
+
The endpoint handles the following common errors:
|
| 143 |
+
|
| 144 |
+
- Missing or invalid request parameters: Returns a 400 Bad Request error.
|
| 145 |
+
- Authentication failure: Returns a 401 Unauthorized error if the `x-api-key` header is missing or invalid.
|
| 146 |
+
- Unexpected exceptions: Returns a 500 Internal Server Error if an unexpected exception occurs during the media cut process.
|
| 147 |
+
|
| 148 |
+
The main application context (`app.py`) also includes error handling for queue overload. If the maximum queue length is reached, the endpoint returns a 429 Too Many Requests error.
|
| 149 |
+
|
| 150 |
+
## 6. Usage Notes
|
| 151 |
+
|
| 152 |
+
- The `media_url` parameter must be a valid URL pointing to a media file (video or audio).
|
| 153 |
+
- The `cuts` parameter must be an array of objects, where each object specifies a start and end time for a cut segment in the format `hh:mm:ss.ms`.
|
| 154 |
+
- The optional encoding parameters (`video_codec`, `video_preset`, `video_crf`, `audio_codec`, `audio_bitrate`) can be used to customize the output file encoding settings.
|
| 155 |
+
- The `webhook_url` parameter is optional and can be used to receive a webhook notification upon completion of the task.
|
| 156 |
+
- The `id` parameter is optional and can be used to uniquely identify the request.
|
| 157 |
+
|
| 158 |
+
## 7. Common Issues
|
| 159 |
+
|
| 160 |
+
- Providing an invalid or inaccessible `media_url`.
|
| 161 |
+
- Providing invalid or out-of-range values for the encoding parameters.
|
| 162 |
+
- Providing overlapping or invalid cut segments in the `cuts` parameter.
|
| 163 |
+
|
| 164 |
+
## 8. Best Practices
|
| 165 |
+
|
| 166 |
+
- Validate the input parameters on the client-side before sending the request.
|
| 167 |
+
- Use the `webhook_url` parameter to receive notifications and handle the response asynchronously.
|
| 168 |
+
- Monitor the `queue_length` parameter in the response to manage the load on the API.
|
| 169 |
+
- Use the `id` parameter to correlate requests and responses for better tracking and debugging.
|
docs/media/download.md
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Media Download API Endpoint Documentation
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
The `/v1/BETA/media/download` endpoint provides a powerful interface for downloading media content from various online sources using the yt-dlp library. This endpoint is part of the v1 media services in the API structure, allowing users to download videos, extract audio, and retrieve thumbnails and subtitles from supported platforms. The endpoint handles authentication, request validation, and queues tasks for processing, making it suitable for handling resource-intensive media downloads without blocking the main application thread.
|
| 6 |
+
|
| 7 |
+
## Endpoint
|
| 8 |
+
|
| 9 |
+
- **URL**: `/v1/BETA/media/download`
|
| 10 |
+
- **Method**: `POST`
|
| 11 |
+
- **Blueprint**: `v1_media_download_bp`
|
| 12 |
+
|
| 13 |
+
## Request
|
| 14 |
+
|
| 15 |
+
### Headers
|
| 16 |
+
|
| 17 |
+
- `x-api-key`: Required for authentication (handled by the `@authenticate` decorator)
|
| 18 |
+
|
| 19 |
+
### Body Parameters
|
| 20 |
+
|
| 21 |
+
#### Required Parameters
|
| 22 |
+
|
| 23 |
+
| Parameter | Type | Description |
|
| 24 |
+
|-----------|------|-------------|
|
| 25 |
+
| `media_url` | string (URI format) | The URL of the media to download |
|
| 26 |
+
|
| 27 |
+
#### Optional Parameters
|
| 28 |
+
|
| 29 |
+
| Parameter | Type | Description |
|
| 30 |
+
|-----------|------|-------------|
|
| 31 |
+
| `webhook_url` | string (URI format) | URL to receive the result when processing is complete |
|
| 32 |
+
| `id` | string | Custom identifier for tracking the request |
|
| 33 |
+
| `cookie` | string | Path to cookie file, URL to cookie file, or cookie string in Netscape format |
|
| 34 |
+
| `cloud_upload` | boolean | When true (default), the downloaded media will be uploaded to cloud storage and a cloud URL will be returned. When false, the direct download URL of the media will be returned instead. |
|
| 35 |
+
|
| 36 |
+
#### Format Options (Optional)
|
| 37 |
+
|
| 38 |
+
```json
|
| 39 |
+
"format": {
|
| 40 |
+
"quality": "string", // Quality specification (e.g., "best")
|
| 41 |
+
"format_id": "string", // Specific format ID
|
| 42 |
+
"resolution": "string", // Resolution specification (e.g., "720p")
|
| 43 |
+
"video_codec": "string", // Video codec preference
|
| 44 |
+
"audio_codec": "string" // Audio codec preference
|
| 45 |
+
}
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
#### Audio Options (Optional)
|
| 49 |
+
|
| 50 |
+
```json
|
| 51 |
+
"audio": {
|
| 52 |
+
"extract": boolean, // Whether to extract audio
|
| 53 |
+
"format": "string", // Audio format (e.g., "mp3", "m4a")
|
| 54 |
+
"quality": "string" // Audio quality specification
|
| 55 |
+
}
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
#### Thumbnail Options (Optional)
|
| 59 |
+
|
| 60 |
+
```json
|
| 61 |
+
"thumbnails": {
|
| 62 |
+
"download": boolean, // Whether to download thumbnails
|
| 63 |
+
"download_all": boolean, // Whether to download all available thumbnails
|
| 64 |
+
"formats": ["string"], // Array of thumbnail formats to download
|
| 65 |
+
"convert": boolean, // Whether to convert thumbnails
|
| 66 |
+
"embed_in_audio": boolean // Whether to embed thumbnails in audio files
|
| 67 |
+
}
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
#### Subtitle Options (Optional)
|
| 71 |
+
|
| 72 |
+
```json
|
| 73 |
+
"subtitles": {
|
| 74 |
+
"download": boolean, // Whether to download subtitles
|
| 75 |
+
"languages": ["string"], // Array of language codes for subtitles
|
| 76 |
+
"formats": ["string"] // Array of subtitle formats to download
|
| 77 |
+
}
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
#### Download Options (Optional)
|
| 81 |
+
|
| 82 |
+
```json
|
| 83 |
+
"download": {
|
| 84 |
+
"max_filesize": integer, // Maximum file size in bytes
|
| 85 |
+
"rate_limit": "string", // Download rate limit (e.g., "50K")
|
| 86 |
+
"retries": integer // Number of download retry attempts
|
| 87 |
+
}
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
### Example Request
|
| 91 |
+
|
| 92 |
+
```json
|
| 93 |
+
{
|
| 94 |
+
"media_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
| 95 |
+
"webhook_url": "https://example.com/webhook",
|
| 96 |
+
"id": "custom-request-123",
|
| 97 |
+
"cookie": "# Netscape HTTP Cookie File\n.youtube.com\tTRUE\t/\tFALSE\t0\tCONSENT\tYES+cb",
|
| 98 |
+
"cloud_upload": true,
|
| 99 |
+
"format": {
|
| 100 |
+
"quality": "best",
|
| 101 |
+
"resolution": "720p"
|
| 102 |
+
},
|
| 103 |
+
"audio": {
|
| 104 |
+
"extract": true,
|
| 105 |
+
"format": "mp3"
|
| 106 |
+
},
|
| 107 |
+
"thumbnails": {
|
| 108 |
+
"download": true
|
| 109 |
+
}
|
| 110 |
+
}
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
### Example cURL Command
|
| 114 |
+
|
| 115 |
+
```bash
|
| 116 |
+
curl -X POST \
|
| 117 |
+
https://api.example.com/v1/BETA/media/download \
|
| 118 |
+
-H 'Content-Type: application/json' \
|
| 119 |
+
-H 'x-api-key: your-api-key-here' \
|
| 120 |
+
-d '{
|
| 121 |
+
"media_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
| 122 |
+
"webhook_url": "https://example.com/webhook",
|
| 123 |
+
"id": "custom-request-123",
|
| 124 |
+
"cookie": "# Netscape HTTP Cookie File\n.youtube.com\tTRUE\t/\tFALSE\t0\tCONSENT\tYES+cb",
|
| 125 |
+
"cloud_upload": true,
|
| 126 |
+
"format": {
|
| 127 |
+
"quality": "best",
|
| 128 |
+
"resolution": "720p"
|
| 129 |
+
},
|
| 130 |
+
"audio": {
|
| 131 |
+
"extract": true,
|
| 132 |
+
"format": "mp3"
|
| 133 |
+
},
|
| 134 |
+
"thumbnails": {
|
| 135 |
+
"download": true
|
| 136 |
+
}
|
| 137 |
+
}'
|
| 138 |
+
```
|
| 139 |
+
|
| 140 |
+
## Response
|
| 141 |
+
|
| 142 |
+
### Immediate Response (When Using Webhook)
|
| 143 |
+
|
| 144 |
+
When a webhook URL is provided, the API will queue the task and return an immediate response with a 202 status code:
|
| 145 |
+
|
| 146 |
+
```json
|
| 147 |
+
{
|
| 148 |
+
"code": 202,
|
| 149 |
+
"id": "custom-request-123",
|
| 150 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 151 |
+
"message": "processing",
|
| 152 |
+
"pid": 12345,
|
| 153 |
+
"queue_id": 67890,
|
| 154 |
+
"max_queue_length": "unlimited",
|
| 155 |
+
"queue_length": 3,
|
| 156 |
+
"build_number": "1.0.123"
|
| 157 |
+
}
|
| 158 |
+
```
|
| 159 |
+
|
| 160 |
+
### Success Response (When Not Using Webhook or When Webhook Is Called)
|
| 161 |
+
|
| 162 |
+
```json
|
| 163 |
+
{
|
| 164 |
+
"code": 200,
|
| 165 |
+
"id": "custom-request-123",
|
| 166 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 167 |
+
"response": {
|
| 168 |
+
"media": {
|
| 169 |
+
"media_url": "https://storage.example.com/media/video-123.mp4",
|
| 170 |
+
"title": "Never Gonna Give You Up",
|
| 171 |
+
"format_id": "22",
|
| 172 |
+
"ext": "mp4",
|
| 173 |
+
"resolution": "720p",
|
| 174 |
+
"filesize": 12345678,
|
| 175 |
+
"width": 1280,
|
| 176 |
+
"height": 720,
|
| 177 |
+
"fps": 30,
|
| 178 |
+
"video_codec": "avc1.4d401f",
|
| 179 |
+
"audio_codec": "mp4a.40.2",
|
| 180 |
+
"upload_date": "20090325",
|
| 181 |
+
"duration": 212,
|
| 182 |
+
"view_count": 1234567890,
|
| 183 |
+
"uploader": "Rick Astley",
|
| 184 |
+
"uploader_id": "RickAstleyVEVO",
|
| 185 |
+
"description": "Official music video for Rick Astley - Never Gonna Give You Up"
|
| 186 |
+
},
|
| 187 |
+
"thumbnails": [
|
| 188 |
+
{
|
| 189 |
+
"id": "default",
|
| 190 |
+
"image_url": "https://storage.example.com/media/thumbnail-123.jpg",
|
| 191 |
+
"width": 1280,
|
| 192 |
+
"height": 720,
|
| 193 |
+
"original_format": "jpg",
|
| 194 |
+
"converted": false
|
| 195 |
+
}
|
| 196 |
+
]
|
| 197 |
+
},
|
| 198 |
+
"message": "success",
|
| 199 |
+
"pid": 12345,
|
| 200 |
+
"queue_id": 67890,
|
| 201 |
+
"run_time": 5.123,
|
| 202 |
+
"queue_time": 0.456,
|
| 203 |
+
"total_time": 5.579,
|
| 204 |
+
"queue_length": 2,
|
| 205 |
+
"build_number": "1.0.123"
|
| 206 |
+
}
|
| 207 |
+
```
|
| 208 |
+
|
| 209 |
+
### Error Responses
|
| 210 |
+
|
| 211 |
+
#### Invalid Request (400)
|
| 212 |
+
|
| 213 |
+
```json
|
| 214 |
+
{
|
| 215 |
+
"code": 400,
|
| 216 |
+
"id": "custom-request-123",
|
| 217 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 218 |
+
"message": "Invalid request: 'media_url' is a required property",
|
| 219 |
+
"pid": 12345,
|
| 220 |
+
"queue_id": 67890,
|
| 221 |
+
"queue_length": 2,
|
| 222 |
+
"build_number": "1.0.123"
|
| 223 |
+
}
|
| 224 |
+
```
|
| 225 |
+
|
| 226 |
+
#### Authentication Error (401)
|
| 227 |
+
|
| 228 |
+
```json
|
| 229 |
+
{
|
| 230 |
+
"code": 401,
|
| 231 |
+
"message": "Invalid API key",
|
| 232 |
+
"build_number": "1.0.123"
|
| 233 |
+
}
|
| 234 |
+
```
|
| 235 |
+
|
| 236 |
+
#### Queue Full (429)
|
| 237 |
+
|
| 238 |
+
```json
|
| 239 |
+
{
|
| 240 |
+
"code": 429,
|
| 241 |
+
"id": "custom-request-123",
|
| 242 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 243 |
+
"message": "MAX_QUEUE_LENGTH (100) reached",
|
| 244 |
+
"pid": 12345,
|
| 245 |
+
"queue_id": 67890,
|
| 246 |
+
"queue_length": 100,
|
| 247 |
+
"build_number": "1.0.123"
|
| 248 |
+
}
|
| 249 |
+
```
|
| 250 |
+
|
| 251 |
+
#### Server Error (500)
|
| 252 |
+
|
| 253 |
+
```json
|
| 254 |
+
{
|
| 255 |
+
"code": 500,
|
| 256 |
+
"id": "custom-request-123",
|
| 257 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 258 |
+
"message": "Error during download process - HTTP Error 403: Forbidden",
|
| 259 |
+
"pid": 12345,
|
| 260 |
+
"queue_id": 67890,
|
| 261 |
+
"queue_length": 2,
|
| 262 |
+
"build_number": "1.0.123"
|
| 263 |
+
}
|
| 264 |
+
```
|
| 265 |
+
|
| 266 |
+
## Error Handling
|
| 267 |
+
|
| 268 |
+
The endpoint handles various error scenarios:
|
| 269 |
+
|
| 270 |
+
- **Missing Required Parameters**: Returns a 400 status code with details about the missing parameter
|
| 271 |
+
- **Invalid Parameter Format**: Returns a 400 status code if parameters don't match the expected format
|
| 272 |
+
- **Authentication Failures**: Returns a 401 status code if the API key is invalid or missing
|
| 273 |
+
- **Queue Limits**: Returns a 429 status code if the task queue is full (when MAX_QUEUE_LENGTH is set)
|
| 274 |
+
- **Download Failures**: Returns a 500 status code with details about the download failure
|
| 275 |
+
- **Media Source Errors**: Returns a 500 status code if the media source is unavailable or restricted
|
| 276 |
+
|
| 277 |
+
## Usage Notes
|
| 278 |
+
|
| 279 |
+
1. **Webhook Handling**:
|
| 280 |
+
- When providing a `webhook_url`, the request will be queued and processed asynchronously
|
| 281 |
+
- Without a `webhook_url`, the request will be processed synchronously, which may lead to longer response times
|
| 282 |
+
|
| 283 |
+
2. **Format Selection**:
|
| 284 |
+
- The `format` options allow fine-grained control over the downloaded media quality
|
| 285 |
+
- When multiple format options are specified, they are combined with a '+' separator
|
| 286 |
+
|
| 287 |
+
3. **Audio Extraction**:
|
| 288 |
+
- Setting `audio.extract` to `true` will extract audio from the media
|
| 289 |
+
- Specify `audio.format` to control the output audio format (e.g., "mp3", "m4a")
|
| 290 |
+
|
| 291 |
+
4. **Thumbnail Handling**:
|
| 292 |
+
- When `thumbnails.download` is `true`, the API will download and provide URLs for thumbnails
|
| 293 |
+
- Use `thumbnails.download_all` to retrieve all available thumbnails
|
| 294 |
+
|
| 295 |
+
5. **Rate Limiting**:
|
| 296 |
+
- Use `download.rate_limit` to control download speed (e.g., "50K" for 50 KB/s)
|
| 297 |
+
- This can help prevent IP blocking from some media sources
|
| 298 |
+
|
| 299 |
+
## Common Issues
|
| 300 |
+
|
| 301 |
+
1. **Geo-restricted Content**: Some media may be unavailable in certain regions
|
| 302 |
+
2. **Rate Limiting**: Media sources may rate-limit or block frequent downloads
|
| 303 |
+
3. **Large File Downloads**: Very large files may time out during download
|
| 304 |
+
4. **Format Availability**: Not all requested formats may be available for all media sources
|
| 305 |
+
5. **Webhook Failures**: If the webhook URL is unreachable, you won't receive the final result
|
| 306 |
+
6. **Queue Overflow**: Requests may be rejected if the processing queue is full
|
| 307 |
+
|
| 308 |
+
## Best Practices
|
| 309 |
+
|
| 310 |
+
1. **Use Webhooks for Large Downloads**: Always use webhooks for potentially large or slow downloads to avoid timeout issues
|
| 311 |
+
2. **Specify Format Constraints**: Be specific about format requirements to avoid unnecessarily large downloads
|
| 312 |
+
3. **Handle Thumbnails Separately**: For efficiency, only request thumbnails when needed
|
| 313 |
+
4. **Implement Retry Logic**: Implement client-side retry logic for handling temporary failures
|
| 314 |
+
5. **Monitor Queue Length**: Check the `queue_length` in responses to gauge system load
|
| 315 |
+
6. **Use Reasonable Rate Limits**: Set appropriate `download.rate_limit` values to avoid being blocked by media sources
|
| 316 |
+
7. **Validate Media URLs**: Ensure media URLs are valid and accessible before submitting
|
| 317 |
+
8. **Store Downloaded Media**: The cloud URLs provided in responses may have expiration times, so download and store important media promptly
|
docs/media/feedback.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Media Feedback Portal
|
| 2 |
+
|
| 3 |
+
This endpoint serves a static web page for collecting media feedback.
|
| 4 |
+
|
| 5 |
+
## Endpoint
|
| 6 |
+
|
| 7 |
+
```
|
| 8 |
+
GET /v1/media/feedback
|
| 9 |
+
```
|
| 10 |
+
|
| 11 |
+
### Authentication
|
| 12 |
+
|
| 13 |
+
This endpoint does not require authentication and is publicly accessible.
|
| 14 |
+
|
| 15 |
+
### Response
|
| 16 |
+
|
| 17 |
+
Returns the HTML feedback form page.
|
| 18 |
+
|
| 19 |
+
## Static Files
|
| 20 |
+
|
| 21 |
+
Additional static files (CSS, JavaScript, images) can be accessed at:
|
| 22 |
+
|
| 23 |
+
```
|
| 24 |
+
GET /v1/media/feedback/<filename>
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
Replace `<filename>` with the path to the static resource relative to the static directory.
|
| 28 |
+
|
| 29 |
+
## Development
|
| 30 |
+
|
| 31 |
+
The static website files are stored in:
|
| 32 |
+
|
| 33 |
+
```
|
| 34 |
+
services/v1/media/feedback/static/
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
This directory contains:
|
| 38 |
+
|
| 39 |
+
- `index.html` - Main HTML file
|
| 40 |
+
- `css/styles.css` - Stylesheet
|
| 41 |
+
- `js/script.js` - JavaScript code
|
| 42 |
+
- `images/` - Directory for image assets
|
| 43 |
+
|
| 44 |
+
To modify the feedback page, edit these files directly.
|
docs/media/generate_ass.md
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ASS Subtitle Generation Endpoint (v1)
|
| 2 |
+
|
| 3 |
+
## 1. Overview
|
| 4 |
+
|
| 5 |
+
The `/v1/media/generate/ass` endpoint is part of the Media API and is responsible for generating an ASS (Advanced SubStation Alpha) subtitle file from a media file (typically a video or audio). It accepts a media URL and various styling options for the subtitles. The endpoint utilizes the `generate_ass_captions_v1` service to generate the ASS file, which is then uploaded to cloud storage, and the cloud URL is returned in the response.
|
| 6 |
+
|
| 7 |
+
## 2. Endpoint
|
| 8 |
+
|
| 9 |
+
**URL:** `/v1/media/generate/ass`
|
| 10 |
+
**Method:** `POST`
|
| 11 |
+
|
| 12 |
+
## 3. Request
|
| 13 |
+
|
| 14 |
+
### Headers
|
| 15 |
+
|
| 16 |
+
- `x-api-key`: Required. The API key for authentication.
|
| 17 |
+
|
| 18 |
+
### Body Parameters
|
| 19 |
+
|
| 20 |
+
The request body must be a JSON object with the following properties:
|
| 21 |
+
> **Note:** `canvas_width` and `canvas_height` are recommended for audio files (e.g., MP3) to control the subtitle canvas size.
|
| 22 |
+
|
| 23 |
+
- `media_url` (string, required): The URL of the media file (video or audio) to generate subtitles for.
|
| 24 |
+
- `canvas_width` (integer, optional): Subtitle canvas width in pixels.
|
| 25 |
+
- `canvas_height` (integer, optional): Subtitle canvas height in pixels.
|
| 26 |
+
- `settings` (object, optional): An object containing various styling options for the subtitles. See the schema below for available options.
|
| 27 |
+
- `replace` (array, optional): An array of objects with `find` and `replace` properties, specifying text replacements to be made in the subtitles.
|
| 28 |
+
- `exclude_time_ranges` (array, optional): List of time ranges to skip when generating subtitles. Each item must be an object with:
|
| 29 |
+
- `start`: (string, required) The start time of the excluded range, as a string timecode in `hh:mm:ss.ms` format (e.g., `00:01:23.456`).
|
| 30 |
+
- `end`: (string, required) The end time, as a string timecode in `hh:mm:ss.ms` format, which must be strictly greater than `start`.
|
| 31 |
+
- `language` (string, optional): The language code for the subtitles (e.g., "en", "fr"). Defaults to "auto".
|
| 32 |
+
- `webhook_url` (string, optional): A URL to receive a webhook notification when the subtitle generation process is complete.
|
| 33 |
+
- `id` (string, optional): An identifier for the request.
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
#### Settings Schema
|
| 38 |
+
|
| 39 |
+
```json
|
| 40 |
+
{
|
| 41 |
+
"type": "object",
|
| 42 |
+
"properties": {
|
| 43 |
+
"line_color": {"type": "string"},
|
| 44 |
+
"word_color": {"type": "string"},
|
| 45 |
+
"outline_color": {"type": "string"},
|
| 46 |
+
"all_caps": {"type": "boolean"},
|
| 47 |
+
"max_words_per_line": {"type": "integer"},
|
| 48 |
+
"x": {"type": "integer"},
|
| 49 |
+
"y": {"type": "integer"},
|
| 50 |
+
"position": {
|
| 51 |
+
"type": "string",
|
| 52 |
+
"enum": [
|
| 53 |
+
"bottom_left", "bottom_center", "bottom_right",
|
| 54 |
+
"middle_left", "middle_center", "middle_right",
|
| 55 |
+
"top_left", "top_center", "top_right"
|
| 56 |
+
]
|
| 57 |
+
},
|
| 58 |
+
"alignment": {
|
| 59 |
+
"type": "string",
|
| 60 |
+
"enum": ["left", "center", "right"]
|
| 61 |
+
},
|
| 62 |
+
"font_family": {"type": "string"},
|
| 63 |
+
"font_size": {"type": "integer"},
|
| 64 |
+
"bold": {"type": "boolean"},
|
| 65 |
+
"italic": {"type": "boolean"},
|
| 66 |
+
"underline": {"type": "boolean"},
|
| 67 |
+
"strikeout": {"type": "boolean"},
|
| 68 |
+
"style": {
|
| 69 |
+
"type": "string",
|
| 70 |
+
"enum": [
|
| 71 |
+
"classic", // Regular subtitle with all text displayed at once
|
| 72 |
+
"karaoke", // Highlights words sequentially in a karaoke style
|
| 73 |
+
"highlight", // Shows full text but highlights the current word
|
| 74 |
+
"underline", // Shows full text but underlines the current word
|
| 75 |
+
"word_by_word" // Shows one word at a time
|
| 76 |
+
]
|
| 77 |
+
},
|
| 78 |
+
"outline_width": {"type": "integer"},
|
| 79 |
+
"spacing": {"type": "integer"},
|
| 80 |
+
"angle": {"type": "integer"},
|
| 81 |
+
"shadow_offset": {"type": "integer"}
|
| 82 |
+
},
|
| 83 |
+
"additionalProperties": false
|
| 84 |
+
}
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
### Example Requests
|
| 88 |
+
|
| 89 |
+
#### Example 1: Basic Automatic Subtitle Generation
|
| 90 |
+
```json
|
| 91 |
+
{
|
| 92 |
+
"media_url": "https://example.com/video.mp4"
|
| 93 |
+
}
|
| 94 |
+
```
|
| 95 |
+
This minimal request will automatically transcribe the media and generate white subtitles at the bottom center.
|
| 96 |
+
|
| 97 |
+
#### Example 2: Custom Styling
|
| 98 |
+
```json
|
| 99 |
+
{
|
| 100 |
+
"media_url": "https://example.com/video.mp4",
|
| 101 |
+
"settings": {
|
| 102 |
+
"style": "classic",
|
| 103 |
+
"line_color": "#FFFFFF",
|
| 104 |
+
"outline_color": "#000000",
|
| 105 |
+
"position": "bottom_center",
|
| 106 |
+
"alignment": "center",
|
| 107 |
+
"font_family": "Arial",
|
| 108 |
+
"font_size": 24,
|
| 109 |
+
"bold": true
|
| 110 |
+
}
|
| 111 |
+
}
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
#### Example 3: Karaoke-Style Subtitles with Advanced Options
|
| 115 |
+
```json
|
| 116 |
+
{
|
| 117 |
+
"media_url": "https://example.com/video.mp4",
|
| 118 |
+
"settings": {
|
| 119 |
+
"line_color": "#FFFFFF",
|
| 120 |
+
"word_color": "#FFFF00",
|
| 121 |
+
"outline_color": "#000000",
|
| 122 |
+
"all_caps": false,
|
| 123 |
+
"max_words_per_line": 10,
|
| 124 |
+
"position": "bottom_center",
|
| 125 |
+
"alignment": "center",
|
| 126 |
+
"font_family": "Arial",
|
| 127 |
+
"font_size": 24,
|
| 128 |
+
"bold": false,
|
| 129 |
+
"italic": false,
|
| 130 |
+
"style": "karaoke",
|
| 131 |
+
"outline_width": 2,
|
| 132 |
+
"shadow_offset": 2
|
| 133 |
+
},
|
| 134 |
+
"replace": [
|
| 135 |
+
{
|
| 136 |
+
"find": "um",
|
| 137 |
+
"replace": ""
|
| 138 |
+
},
|
| 139 |
+
{
|
| 140 |
+
"find": "like",
|
| 141 |
+
"replace": ""
|
| 142 |
+
}
|
| 143 |
+
],
|
| 144 |
+
"webhook_url": "https://example.com/webhook",
|
| 145 |
+
"id": "request-123",
|
| 146 |
+
"language": "en"
|
| 147 |
+
}
|
| 148 |
+
```
|
| 149 |
+
|
| 150 |
+
#### Example 4: Excluding Time Ranges from Subtitle Generation
|
| 151 |
+
```json
|
| 152 |
+
{
|
| 153 |
+
"media_url": "https://example.com/video.mp4",
|
| 154 |
+
"settings": {
|
| 155 |
+
"style": "classic",
|
| 156 |
+
"line_color": "#FFFFFF",
|
| 157 |
+
"outline_color": "#000000",
|
| 158 |
+
"position": "bottom_center",
|
| 159 |
+
"font_family": "Arial",
|
| 160 |
+
"font_size": 24
|
| 161 |
+
},
|
| 162 |
+
"exclude_time_ranges": [
|
| 163 |
+
{ "start": "00:00:10.000", "end": "00:00:20.000" },
|
| 164 |
+
{ "start": "00:00:30.000", "end": "00:00:40.000" }
|
| 165 |
+
]
|
| 166 |
+
}
|
| 167 |
+
```
|
| 168 |
+
|
| 169 |
+
#### Example 5: Generating Subtitles for an MP3 (Audio) File
|
| 170 |
+
```json
|
| 171 |
+
{
|
| 172 |
+
"canvas_width": 1280,
|
| 173 |
+
"canvas_height": 720,
|
| 174 |
+
"media_url": "https://example.com/audio.mp3",
|
| 175 |
+
"settings": {
|
| 176 |
+
"style": "classic",
|
| 177 |
+
"font_family": "Arial",
|
| 178 |
+
"font_size": 32,
|
| 179 |
+
"line_color": "#FFFFFF",
|
| 180 |
+
"outline_color": "#000000"
|
| 181 |
+
}
|
| 182 |
+
}
|
| 183 |
+
```
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
```bash
|
| 187 |
+
curl -X POST \
|
| 188 |
+
-H "x-api-key: YOUR_API_KEY" \
|
| 189 |
+
-H "Content-Type: application/json" \
|
| 190 |
+
-d '{
|
| 191 |
+
"media_url": "https://example.com/video.mp4",
|
| 192 |
+
"settings": {
|
| 193 |
+
"line_color": "#FFFFFF",
|
| 194 |
+
"word_color": "#FFFF00",
|
| 195 |
+
"outline_color": "#000000",
|
| 196 |
+
"all_caps": false,
|
| 197 |
+
"max_words_per_line": 10,
|
| 198 |
+
"position": "bottom_center",
|
| 199 |
+
"alignment": "center",
|
| 200 |
+
"font_family": "Arial",
|
| 201 |
+
"font_size": 24,
|
| 202 |
+
"style": "karaoke",
|
| 203 |
+
"outline_width": 2
|
| 204 |
+
},
|
| 205 |
+
"replace": [
|
| 206 |
+
{
|
| 207 |
+
"find": "um",
|
| 208 |
+
"replace": ""
|
| 209 |
+
}
|
| 210 |
+
],
|
| 211 |
+
"id": "custom-request-id"
|
| 212 |
+
}' \
|
| 213 |
+
https://your-api-endpoint.com/v1/media/generate/ass
|
| 214 |
+
```
|
| 215 |
+
|
| 216 |
+
## 4. Response
|
| 217 |
+
|
| 218 |
+
### Success Response
|
| 219 |
+
|
| 220 |
+
The response will be a JSON object with the following properties:
|
| 221 |
+
|
| 222 |
+
- `code` (integer): The HTTP status code (200 for success).
|
| 223 |
+
- `id` (string): The request identifier, if provided in the request.
|
| 224 |
+
- `job_id` (string): A unique identifier for the job.
|
| 225 |
+
- `response` (string): The cloud URL of the generated ASS subtitle file.
|
| 226 |
+
- `message` (string): A success message.
|
| 227 |
+
- `pid` (integer): The process ID of the worker that processed the request.
|
| 228 |
+
- `queue_id` (integer): The ID of the queue used for processing the request.
|
| 229 |
+
- `run_time` (float): The time taken to process the request (in seconds).
|
| 230 |
+
- `queue_time` (float): The time the request spent in the queue (in seconds).
|
| 231 |
+
- `total_time` (float): The total time taken for the request (in seconds).
|
| 232 |
+
- `queue_length` (integer): The current length of the processing queue.
|
| 233 |
+
- `build_number` (string): The build number of the application.
|
| 234 |
+
|
| 235 |
+
Example:
|
| 236 |
+
|
| 237 |
+
```json
|
| 238 |
+
{
|
| 239 |
+
"code": 200,
|
| 240 |
+
"id": "request-123",
|
| 241 |
+
"job_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
|
| 242 |
+
"response": "https://cloud.example.com/generated-subtitles.ass",
|
| 243 |
+
"message": "success",
|
| 244 |
+
"pid": 12345,
|
| 245 |
+
"queue_id": 140682639937472,
|
| 246 |
+
"run_time": 2.345,
|
| 247 |
+
"queue_time": 0.010,
|
| 248 |
+
"total_time": 2.355,
|
| 249 |
+
"queue_length": 0,
|
| 250 |
+
"build_number": "1.0.0"
|
| 251 |
+
}
|
| 252 |
+
```
|
| 253 |
+
|
| 254 |
+
### Error Responses
|
| 255 |
+
|
| 256 |
+
#### Missing or Invalid Parameters
|
| 257 |
+
|
| 258 |
+
**Status Code:** 400 Bad Request
|
| 259 |
+
|
| 260 |
+
```json
|
| 261 |
+
{
|
| 262 |
+
"code": 400,
|
| 263 |
+
"id": "request-123",
|
| 264 |
+
"job_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
|
| 265 |
+
"message": "Missing or invalid parameters",
|
| 266 |
+
"pid": 12345,
|
| 267 |
+
"queue_id": 140682639937472,
|
| 268 |
+
"queue_length": 0,
|
| 269 |
+
"build_number": "1.0.0"
|
| 270 |
+
}
|
| 271 |
+
```
|
| 272 |
+
|
| 273 |
+
#### Font Error
|
| 274 |
+
|
| 275 |
+
**Status Code:** 400 Bad Request
|
| 276 |
+
|
| 277 |
+
```json
|
| 278 |
+
{
|
| 279 |
+
"code": 400,
|
| 280 |
+
"error": "The requested font 'InvalidFont' is not available. Please choose from the available fonts.",
|
| 281 |
+
"available_fonts": ["Arial", "Times New Roman", "Courier New", ...],
|
| 282 |
+
"pid": 12345,
|
| 283 |
+
"queue_id": 140682639937472,
|
| 284 |
+
"queue_length": 0,
|
| 285 |
+
"build_number": "1.0.0"
|
| 286 |
+
}
|
| 287 |
+
```
|
| 288 |
+
|
| 289 |
+
#### Internal Server Error
|
| 290 |
+
|
| 291 |
+
**Status Code:** 500 Internal Server Error
|
| 292 |
+
|
| 293 |
+
```json
|
| 294 |
+
{
|
| 295 |
+
"code": 500,
|
| 296 |
+
"id": "request-123",
|
| 297 |
+
"job_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
|
| 298 |
+
"error": "An unexpected error occurred during the subtitle generation process.",
|
| 299 |
+
"pid": 12345,
|
| 300 |
+
"queue_id": 140682639937472,
|
| 301 |
+
"queue_length": 0,
|
| 302 |
+
"build_number": "1.0.0"
|
| 303 |
+
}
|
| 304 |
+
```
|
| 305 |
+
|
| 306 |
+
## 5. Error Handling
|
| 307 |
+
|
| 308 |
+
The endpoint handles the following common errors:
|
| 309 |
+
|
| 310 |
+
- **Missing or Invalid Parameters**: If any required parameters are missing or invalid, a 400 Bad Request error is returned with a descriptive error message.
|
| 311 |
+
- **Font Error**: If the requested font is not available, a 400 Bad Request error is returned with a list of available fonts.
|
| 312 |
+
- **Internal Server Error**: If an unexpected error occurs during the subtitle generation process, a 500 Internal Server Error is returned with an error message.
|
| 313 |
+
|
| 314 |
+
Additionally, the main application context (`app.py`) includes error handling for queue overload. If the maximum queue length (`MAX_QUEUE_LENGTH`) is set and the queue size reaches that limit, a 429 Too Many Requests error is returned with a descriptive message.
|
| 315 |
+
|
| 316 |
+
## 6. Usage Notes
|
| 317 |
+
|
| 318 |
+
- The `media_url` parameter must be a valid URL pointing to a video or audio file.
|
| 319 |
+
- The `settings` parameter allows for customization of the subtitle appearance and behavior:
|
| 320 |
+
- `style` determines how subtitles are displayed, with options including:
|
| 321 |
+
- `classic`: Regular subtitle with all text displayed at once
|
| 322 |
+
- `karaoke`: Highlights words sequentially in a karaoke style as they're spoken
|
| 323 |
+
- `highlight`: Shows the full subtitle text but highlights each word as it's spoken
|
| 324 |
+
- `underline`: Shows the full subtitle text but underlines each word as it's spoken
|
| 325 |
+
- `word_by_word`: Shows only one word at a time
|
| 326 |
+
- `position` can be used to place subtitles in one of nine positions on the screen
|
| 327 |
+
- `alignment` determines text alignment within the position (left, center, right)
|
| 328 |
+
- `font_family` can be any available system font
|
| 329 |
+
- Color options can be set using hex codes (e.g., "#FFFFFF" for white)
|
| 330 |
+
- The `replace` parameter can be used to perform text replacements in the subtitles (useful for correcting words or censoring content).
|
| 331 |
+
- The `webhook_url` parameter is optional and can be used to receive a notification when the subtitle generation process is complete.
|
| 332 |
+
- The `id` parameter is optional and can be used to identify the request in webhook responses.
|
| 333 |
+
- The `language` parameter is optional and can be used to specify the language of the subtitles for transcription. If not provided, the language will be automatically detected.
|
| 334 |
+
- The `exclude_time_ranges` parameter can be used to specify time ranges to be excluded from subtitle generation.
|
| 335 |
+
- If either `canvas_width` or `canvas_height` is provided, both must be provided and must be greater than 0.
|
| 336 |
+
|
| 337 |
+
## 7. Common Issues
|
| 338 |
+
|
| 339 |
+
- Providing an invalid or inaccessible `media_url`.
|
| 340 |
+
- Requesting an unavailable font in the `settings` object.
|
| 341 |
+
- Using this endpoint with an audio-only file (e.g., MP3) and not providing both `canvas_width` and `canvas_height`. For audio files, you must specify both dimensions to generate a valid ASS subtitle file.
|
| 342 |
+
- Exceeding the maximum queue length, resulting in a 429 Too Many Requests error.
|
| 343 |
+
|
| 344 |
+
## 8. Best Practices
|
| 345 |
+
|
| 346 |
+
- Validate the `media_url` parameter before sending the request to ensure it points to a valid and accessible media file.
|
| 347 |
+
- Use the `webhook_url` parameter to receive notifications about the subtitle generation process, rather than polling the API for updates.
|
| 348 |
+
- Provide descriptive and meaningful `id` values to easily identify requests in logs and responses.
|
| 349 |
+
- Use the `replace` parameter judiciously to avoid unintended text replacements in the subtitles.
|
| 350 |
+
- Consider caching the generated ASS files for frequently requested media to improve performance and reduce processing time.
|
docs/media/media_transcribe.md
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Media Transcription API Documentation
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
The Media Transcription endpoint is part of the v1 API suite, providing audio/video transcription and translation capabilities. This endpoint leverages a queuing system for handling long-running transcription tasks, with webhook support for asynchronous processing. It's integrated into the main Flask application as a Blueprint and supports both direct response and cloud storage options for the transcription results.
|
| 5 |
+
|
| 6 |
+
## Endpoint
|
| 7 |
+
- **URL**: `/v1/media/transcribe`
|
| 8 |
+
- **Method**: `POST`
|
| 9 |
+
- **Blueprint**: `v1_media_transcribe_bp`
|
| 10 |
+
|
| 11 |
+
## Request
|
| 12 |
+
|
| 13 |
+
### Headers
|
| 14 |
+
- `x-api-key`: Required. Authentication key for API access.
|
| 15 |
+
- `Content-Type`: Required. Must be `application/json`.
|
| 16 |
+
|
| 17 |
+
### Body Parameters
|
| 18 |
+
|
| 19 |
+
#### Required Parameters
|
| 20 |
+
- `media_url` (string)
|
| 21 |
+
- Format: URI
|
| 22 |
+
- Description: URL of the media file to be transcribed
|
| 23 |
+
|
| 24 |
+
#### Optional Parameters
|
| 25 |
+
- `task` (string)
|
| 26 |
+
- Allowed values: `"transcribe"`, `"translate"`
|
| 27 |
+
- Default: `"transcribe"`
|
| 28 |
+
- Description: Specifies whether to transcribe or translate the audio
|
| 29 |
+
|
| 30 |
+
- `include_text` (boolean)
|
| 31 |
+
- Default: `true`
|
| 32 |
+
- Description: Include plain text transcription in the response
|
| 33 |
+
|
| 34 |
+
- `include_srt` (boolean)
|
| 35 |
+
- Default: `false`
|
| 36 |
+
- Description: Include SRT format subtitles in the response
|
| 37 |
+
|
| 38 |
+
- `include_segments` (boolean)
|
| 39 |
+
- Default: `false`
|
| 40 |
+
- Description: Include timestamped segments in the response
|
| 41 |
+
|
| 42 |
+
- `word_timestamps` (boolean)
|
| 43 |
+
- Default: `false`
|
| 44 |
+
- Description: Include timestamps for individual words
|
| 45 |
+
|
| 46 |
+
- `response_type` (string)
|
| 47 |
+
- Allowed values: `"direct"`, `"cloud"`
|
| 48 |
+
- Default: `"direct"`
|
| 49 |
+
- Description: Whether to return results directly or as cloud storage URLs
|
| 50 |
+
|
| 51 |
+
- `language` (string)
|
| 52 |
+
- Optional
|
| 53 |
+
- Description: Source language code for transcription
|
| 54 |
+
|
| 55 |
+
- `webhook_url` (string)
|
| 56 |
+
- Format: URI
|
| 57 |
+
- Description: URL to receive the transcription results asynchronously
|
| 58 |
+
|
| 59 |
+
- `id` (string)
|
| 60 |
+
- Description: Custom identifier for the transcription job
|
| 61 |
+
|
| 62 |
+
- `max_words_per_line` (integer)
|
| 63 |
+
- Minimum: 1
|
| 64 |
+
- Description: Controls the maximum number of words per line in the SRT file. When specified, each segment's text will be split into multiple lines with at most the specified number of words per line.
|
| 65 |
+
|
| 66 |
+
### Example Request
|
| 67 |
+
|
| 68 |
+
```bash
|
| 69 |
+
curl -X POST "https://api.example.com/v1/media/transcribe" \
|
| 70 |
+
-H "x-api-key: your_api_key" \
|
| 71 |
+
-H "Content-Type: application/json" \
|
| 72 |
+
-d '{
|
| 73 |
+
"media_url": "https://example.com/media/file.mp3",
|
| 74 |
+
"task": "transcribe",
|
| 75 |
+
"include_text": true,
|
| 76 |
+
"include_srt": true,
|
| 77 |
+
"include_segments": true,
|
| 78 |
+
"response_type": "cloud",
|
| 79 |
+
"webhook_url": "https://your-webhook.com/callback",
|
| 80 |
+
"id": "custom-job-123",
|
| 81 |
+
"max_words_per_line": 5
|
| 82 |
+
}'
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
## Response
|
| 86 |
+
|
| 87 |
+
### Immediate Response (202 Accepted)
|
| 88 |
+
When a webhook URL is provided, the API returns an immediate acknowledgment:
|
| 89 |
+
|
| 90 |
+
```json
|
| 91 |
+
{
|
| 92 |
+
"code": 202,
|
| 93 |
+
"id": "custom-job-123",
|
| 94 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 95 |
+
"message": "processing",
|
| 96 |
+
"pid": 12345,
|
| 97 |
+
"queue_id": 67890,
|
| 98 |
+
"max_queue_length": "unlimited",
|
| 99 |
+
"queue_length": 1,
|
| 100 |
+
"build_number": "1.0.0"
|
| 101 |
+
}
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
### Success Response (via Webhook)
|
| 105 |
+
For direct response_type:
|
| 106 |
+
|
| 107 |
+
```json
|
| 108 |
+
{
|
| 109 |
+
"endpoint": "/v1/transcribe/media",
|
| 110 |
+
"code": 200,
|
| 111 |
+
"id": "custom-job-123",
|
| 112 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 113 |
+
"response": {
|
| 114 |
+
"text": "Transcribed text content...",
|
| 115 |
+
"srt": "SRT formatted content...",
|
| 116 |
+
"segments": [...],
|
| 117 |
+
"text_url": null,
|
| 118 |
+
"srt_url": null,
|
| 119 |
+
"segments_url": null
|
| 120 |
+
},
|
| 121 |
+
"message": "success",
|
| 122 |
+
"pid": 12345,
|
| 123 |
+
"queue_id": 67890,
|
| 124 |
+
"run_time": 5.234,
|
| 125 |
+
"queue_time": 0.123,
|
| 126 |
+
"total_time": 5.357,
|
| 127 |
+
"queue_length": 0,
|
| 128 |
+
"build_number": "1.0.0"
|
| 129 |
+
}
|
| 130 |
+
```
|
| 131 |
+
|
| 132 |
+
For cloud response_type:
|
| 133 |
+
|
| 134 |
+
```json
|
| 135 |
+
{
|
| 136 |
+
"endpoint": "/v1/transcribe/media",
|
| 137 |
+
"code": 200,
|
| 138 |
+
"id": "custom-job-123",
|
| 139 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 140 |
+
"response": {
|
| 141 |
+
"text": null,
|
| 142 |
+
"srt": null,
|
| 143 |
+
"segments": null,
|
| 144 |
+
"text_url": "https://storage.example.com/text.txt",
|
| 145 |
+
"srt_url": "https://storage.example.com/subtitles.srt",
|
| 146 |
+
"segments_url": "https://storage.example.com/segments.json"
|
| 147 |
+
},
|
| 148 |
+
"message": "success",
|
| 149 |
+
"pid": 12345,
|
| 150 |
+
"queue_id": 67890,
|
| 151 |
+
"run_time": 5.234,
|
| 152 |
+
"queue_time": 0.123,
|
| 153 |
+
"total_time": 5.357,
|
| 154 |
+
"queue_length": 0,
|
| 155 |
+
"build_number": "1.0.0"
|
| 156 |
+
}
|
| 157 |
+
```
|
| 158 |
+
|
| 159 |
+
### Error Responses
|
| 160 |
+
|
| 161 |
+
#### Queue Full (429 Too Many Requests)
|
| 162 |
+
```json
|
| 163 |
+
{
|
| 164 |
+
"code": 429,
|
| 165 |
+
"id": "custom-job-123",
|
| 166 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 167 |
+
"message": "MAX_QUEUE_LENGTH (100) reached",
|
| 168 |
+
"pid": 12345,
|
| 169 |
+
"queue_id": 67890,
|
| 170 |
+
"queue_length": 100,
|
| 171 |
+
"build_number": "1.0.0"
|
| 172 |
+
}
|
| 173 |
+
```
|
| 174 |
+
|
| 175 |
+
#### Server Error (500 Internal Server Error)
|
| 176 |
+
```json
|
| 177 |
+
{
|
| 178 |
+
"endpoint": "/v1/transcribe/media",
|
| 179 |
+
"code": 500,
|
| 180 |
+
"id": "custom-job-123",
|
| 181 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 182 |
+
"response": null,
|
| 183 |
+
"message": "Error message details",
|
| 184 |
+
"pid": 12345,
|
| 185 |
+
"queue_id": 67890,
|
| 186 |
+
"run_time": 0.123,
|
| 187 |
+
"queue_time": 0.056,
|
| 188 |
+
"total_time": 0.179,
|
| 189 |
+
"queue_length": 1,
|
| 190 |
+
"build_number": "1.0.0"
|
| 191 |
+
}
|
| 192 |
+
```
|
| 193 |
+
|
| 194 |
+
## Error Handling
|
| 195 |
+
|
| 196 |
+
### Common Errors
|
| 197 |
+
- **Invalid API Key**: 401 Unauthorized
|
| 198 |
+
- **Invalid JSON Payload**: 400 Bad Request
|
| 199 |
+
- **Missing Required Fields**: 400 Bad Request
|
| 200 |
+
- **Invalid media_url**: 400 Bad Request
|
| 201 |
+
- **Queue Full**: 429 Too Many Requests
|
| 202 |
+
- **Processing Error**: 500 Internal Server Error
|
| 203 |
+
|
| 204 |
+
### Validation Errors
|
| 205 |
+
The endpoint performs strict validation of the request payload using JSON Schema. Common validation errors include:
|
| 206 |
+
- Invalid URI format for media_url or webhook_url
|
| 207 |
+
- Invalid task value (must be "transcribe" or "translate")
|
| 208 |
+
- Invalid response_type value (must be "direct" or "cloud")
|
| 209 |
+
- Unknown properties in the request body
|
| 210 |
+
|
| 211 |
+
## Usage Notes
|
| 212 |
+
|
| 213 |
+
1. **Webhook Processing**
|
| 214 |
+
- When a webhook_url is provided, the request is processed asynchronously
|
| 215 |
+
- The API returns an immediate 202 response with a job_id
|
| 216 |
+
- Final results are sent to the webhook_url when processing completes
|
| 217 |
+
|
| 218 |
+
2. **Queue Management**
|
| 219 |
+
- Requests with webhook_url are queued for processing
|
| 220 |
+
- MAX_QUEUE_LENGTH environment variable controls queue size
|
| 221 |
+
- Set MAX_QUEUE_LENGTH to 0 for unlimited queue size
|
| 222 |
+
|
| 223 |
+
3. **File Management**
|
| 224 |
+
- For cloud response_type, temporary files are automatically cleaned up
|
| 225 |
+
- Results are uploaded to cloud storage before deletion
|
| 226 |
+
- URLs in the response provide access to the stored files
|
| 227 |
+
|
| 228 |
+
4. **SRT Formatting**
|
| 229 |
+
- The `max_words_per_line` parameter allows control over the maximum number of words per line in the SRT file
|
| 230 |
+
- When specified, each segment's text will be split into multiple lines with at most the specified number of words per line
|
| 231 |
+
- This is useful for creating more readable subtitles with consistent line lengths
|
| 232 |
+
|
| 233 |
+
## Common Issues
|
| 234 |
+
|
| 235 |
+
1. **Media Access**
|
| 236 |
+
- Ensure media_url is publicly accessible
|
| 237 |
+
- Verify media file format is supported
|
| 238 |
+
- Check for media file corruption
|
| 239 |
+
|
| 240 |
+
2. **Webhook Delivery**
|
| 241 |
+
- Ensure webhook_url is publicly accessible
|
| 242 |
+
- Implement webhook endpoint retry logic
|
| 243 |
+
- Monitor webhook endpoint availability
|
| 244 |
+
|
| 245 |
+
3. **Resource Usage**
|
| 246 |
+
- Large media files may take significant processing time
|
| 247 |
+
- Monitor queue length for production deployments
|
| 248 |
+
- Consider implementing request size limits
|
| 249 |
+
|
| 250 |
+
## Best Practices
|
| 251 |
+
|
| 252 |
+
1. **Request Handling**
|
| 253 |
+
- Always provide a unique id for job tracking
|
| 254 |
+
- Implement webhook retry logic
|
| 255 |
+
- Store job_id for result correlation
|
| 256 |
+
|
| 257 |
+
2. **Resource Management**
|
| 258 |
+
- Monitor queue length in production
|
| 259 |
+
- Implement appropriate timeout handling
|
| 260 |
+
- Use cloud response_type for large files
|
| 261 |
+
|
| 262 |
+
3. **Error Handling**
|
| 263 |
+
- Implement comprehensive webhook error handling
|
| 264 |
+
- Log job_id with all related operations
|
| 265 |
+
- Monitor processing times and error rates
|
| 266 |
+
|
| 267 |
+
4. **Security**
|
| 268 |
+
- Use HTTPS for media_url and webhook_url
|
| 269 |
+
- Implement webhook authentication
|
| 270 |
+
- Validate media file types before processing
|
docs/media/metadata.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Media Metadata
|
| 2 |
+
|
| 3 |
+
This endpoint extracts detailed metadata from media files (video, audio, image) including format, duration, codec information, resolution, and bitrates.
|
| 4 |
+
|
| 5 |
+
## Endpoint
|
| 6 |
+
|
| 7 |
+
`POST /v1/media/metadata`
|
| 8 |
+
|
| 9 |
+
## Authentication
|
| 10 |
+
|
| 11 |
+
This endpoint requires API authentication. See [Authentication](../toolkit/authenticate.md) for details.
|
| 12 |
+
|
| 13 |
+
## Request
|
| 14 |
+
|
| 15 |
+
```json
|
| 16 |
+
{
|
| 17 |
+
"media_url": "https://example.com/media.mp4",
|
| 18 |
+
"webhook_url": "https://example.com/webhook", // Optional
|
| 19 |
+
"id": "custom-id" // Optional
|
| 20 |
+
}
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
| Parameter | Type | Required | Description |
|
| 24 |
+
|-----------|------|----------|-------------|
|
| 25 |
+
| media_url | string | Yes | URL of the media file to analyze |
|
| 26 |
+
| webhook_url | string | No | URL to receive the processing result |
|
| 27 |
+
| id | string | No | Custom identifier for tracking the request |
|
| 28 |
+
|
| 29 |
+
## Response
|
| 30 |
+
|
| 31 |
+
**Success (200 OK)**
|
| 32 |
+
|
| 33 |
+
```json
|
| 34 |
+
{
|
| 35 |
+
"code": 200,
|
| 36 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 37 |
+
"id": "custom-id",
|
| 38 |
+
"response": {
|
| 39 |
+
"filesize": 15679283,
|
| 40 |
+
"filesize_mb": 14.95,
|
| 41 |
+
"duration": 87.46,
|
| 42 |
+
"duration_formatted": "00:01:27.46",
|
| 43 |
+
"format": "mp4,mov,m4a,3gp,3g2,mj2",
|
| 44 |
+
"overall_bitrate": 1438692,
|
| 45 |
+
"overall_bitrate_mbps": 1.44,
|
| 46 |
+
"has_video": true,
|
| 47 |
+
"video_codec": "h264",
|
| 48 |
+
"video_codec_long": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
|
| 49 |
+
"width": 1920,
|
| 50 |
+
"height": 1080,
|
| 51 |
+
"resolution": "1920x1080",
|
| 52 |
+
"fps": 30.0,
|
| 53 |
+
"video_bitrate": 1300000,
|
| 54 |
+
"video_bitrate_mbps": 1.3,
|
| 55 |
+
"pixel_format": "yuv420p",
|
| 56 |
+
"has_audio": true,
|
| 57 |
+
"audio_codec": "aac",
|
| 58 |
+
"audio_codec_long": "AAC (Advanced Audio Coding)",
|
| 59 |
+
"audio_channels": 2,
|
| 60 |
+
"audio_sample_rate": 48000,
|
| 61 |
+
"audio_sample_rate_khz": 48.0,
|
| 62 |
+
"audio_bitrate": 128000,
|
| 63 |
+
"audio_bitrate_kbps": 128
|
| 64 |
+
},
|
| 65 |
+
"message": "success",
|
| 66 |
+
"run_time": 0.542,
|
| 67 |
+
"queue_time": 0,
|
| 68 |
+
"total_time": 0.542,
|
| 69 |
+
"pid": 12345,
|
| 70 |
+
"queue_id": 67890,
|
| 71 |
+
"queue_length": 0,
|
| 72 |
+
"build_number": "123"
|
| 73 |
+
}
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
For audio-only files, video-related fields will not be included. Similarly, for video files without audio, audio-related fields will not be included.
|
| 77 |
+
|
| 78 |
+
**Queued (202 Accepted)**
|
| 79 |
+
|
| 80 |
+
```json
|
| 81 |
+
{
|
| 82 |
+
"code": 202,
|
| 83 |
+
"id": "custom-id",
|
| 84 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 85 |
+
"message": "processing",
|
| 86 |
+
"pid": 12345,
|
| 87 |
+
"queue_id": 67890,
|
| 88 |
+
"max_queue_length": "unlimited",
|
| 89 |
+
"queue_length": 0,
|
| 90 |
+
"build_number": "123"
|
| 91 |
+
}
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
**Error (4xx/5xx)**
|
| 95 |
+
|
| 96 |
+
```json
|
| 97 |
+
{
|
| 98 |
+
"code": 500,
|
| 99 |
+
"id": "custom-id",
|
| 100 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 101 |
+
"message": "Error extracting metadata: [error details]",
|
| 102 |
+
"pid": 12345,
|
| 103 |
+
"queue_id": 67890,
|
| 104 |
+
"queue_length": 0,
|
| 105 |
+
"build_number": "123"
|
| 106 |
+
}
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
## Example
|
| 110 |
+
|
| 111 |
+
### Request
|
| 112 |
+
|
| 113 |
+
```bash
|
| 114 |
+
curl -X POST https://api.example.com/v1/media/metadata \
|
| 115 |
+
-H "Content-Type: application/json" \
|
| 116 |
+
-H "Authorization: Bearer your_api_key" \
|
| 117 |
+
-d '{
|
| 118 |
+
"media_url": "https://example.com/sample-video.mp4",
|
| 119 |
+
"webhook_url": "https://your-server.com/webhook"
|
| 120 |
+
}'
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
### Response
|
| 124 |
+
|
| 125 |
+
```json
|
| 126 |
+
{
|
| 127 |
+
"code": 200,
|
| 128 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 129 |
+
"response": {
|
| 130 |
+
"filesize": 15679283,
|
| 131 |
+
"filesize_mb": 14.95,
|
| 132 |
+
"duration": 87.46,
|
| 133 |
+
"duration_formatted": "00:01:27.46",
|
| 134 |
+
"format": "mp4",
|
| 135 |
+
"overall_bitrate": 1438692,
|
| 136 |
+
"overall_bitrate_mbps": 1.44,
|
| 137 |
+
"has_video": true,
|
| 138 |
+
"video_codec": "h264",
|
| 139 |
+
"width": 1920,
|
| 140 |
+
"height": 1080,
|
| 141 |
+
"resolution": "1920x1080",
|
| 142 |
+
"fps": 30.0,
|
| 143 |
+
"video_bitrate": 1300000,
|
| 144 |
+
"video_bitrate_mbps": 1.3,
|
| 145 |
+
"has_audio": true,
|
| 146 |
+
"audio_codec": "aac",
|
| 147 |
+
"audio_channels": 2,
|
| 148 |
+
"audio_sample_rate": 48000,
|
| 149 |
+
"audio_bitrate": 128000,
|
| 150 |
+
"audio_bitrate_kbps": 128
|
| 151 |
+
},
|
| 152 |
+
"message": "success",
|
| 153 |
+
"run_time": 0.542,
|
| 154 |
+
"total_time": 0.542
|
| 155 |
+
}
|
| 156 |
+
```
|
docs/media/silence.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Silence Detection Endpoint
|
| 2 |
+
|
| 3 |
+
## 1. Overview
|
| 4 |
+
|
| 5 |
+
The `/v1/media/silence` endpoint is part of the Media API and is designed to detect silence intervals in a given media file. It takes a media URL, along with various parameters for configuring the silence detection process, and returns the detected silence intervals. This endpoint fits into the overall API structure as a part of the version 1 (v1) media-related endpoints.
|
| 6 |
+
|
| 7 |
+
## 2. Endpoint
|
| 8 |
+
|
| 9 |
+
```
|
| 10 |
+
POST /v1/media/silence
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
## 3. Request
|
| 14 |
+
|
| 15 |
+
### Headers
|
| 16 |
+
|
| 17 |
+
- `x-api-key` (required): The API key for authentication.
|
| 18 |
+
|
| 19 |
+
### Body Parameters
|
| 20 |
+
|
| 21 |
+
The request body should be a JSON object with the following parameters:
|
| 22 |
+
|
| 23 |
+
- `media_url` (required, string): The URL of the media file to be processed.
|
| 24 |
+
- `start` (optional, string): The start time for the silence detection process, in the format `HH:MM:SS.ms`. If not provided, the process will start from the beginning of the media file.
|
| 25 |
+
- `end` (optional, string): The end time for the silence detection process, in the format `HH:MM:SS.ms`. If not provided, the process will continue until the end of the media file.
|
| 26 |
+
- `noise` (optional, string): The noise threshold for silence detection, in decibels (dB). Default is `-30dB`.
|
| 27 |
+
- `duration` (required, number): The minimum duration (in seconds) for a silence interval to be considered valid.
|
| 28 |
+
- `mono` (optional, boolean): Whether to process the audio as mono (single channel) or not. Default is `true`.
|
| 29 |
+
- `webhook_url` (required, string): The URL to which the response should be sent as a webhook.
|
| 30 |
+
- `id` (required, string): A unique identifier for the request.
|
| 31 |
+
|
| 32 |
+
The `validate_payload` directive in the routes file enforces the following JSON schema for the request body:
|
| 33 |
+
|
| 34 |
+
```python
|
| 35 |
+
{
|
| 36 |
+
"type": "object",
|
| 37 |
+
"properties": {
|
| 38 |
+
"media_url": {"type": "string", "format": "uri"},
|
| 39 |
+
"start": {"type": "string"},
|
| 40 |
+
"end": {"type": "string"},
|
| 41 |
+
"noise": {"type": "string"},
|
| 42 |
+
"duration": {"type": "number", "minimum": 0.1},
|
| 43 |
+
"mono": {"type": "boolean"},
|
| 44 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 45 |
+
"id": {"type": "string"}
|
| 46 |
+
},
|
| 47 |
+
"required": ["media_url", "duration"],
|
| 48 |
+
"additionalProperties": False
|
| 49 |
+
}
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
### Example Request
|
| 53 |
+
|
| 54 |
+
```json
|
| 55 |
+
{
|
| 56 |
+
"media_url": "https://example.com/audio.mp3",
|
| 57 |
+
"start": "00:00:10.0",
|
| 58 |
+
"end": "00:01:00.0",
|
| 59 |
+
"noise": "-25dB",
|
| 60 |
+
"duration": 0.5,
|
| 61 |
+
"mono": false,
|
| 62 |
+
"webhook_url": "https://example.com/webhook",
|
| 63 |
+
"id": "unique-request-id"
|
| 64 |
+
}
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
```
|
| 68 |
+
curl -X POST \
|
| 69 |
+
https://api.example.com/v1/media/silence \
|
| 70 |
+
-H 'x-api-key: YOUR_API_KEY' \
|
| 71 |
+
-H 'Content-Type: application/json' \
|
| 72 |
+
-d '{
|
| 73 |
+
"media_url": "https://example.com/audio.mp3",
|
| 74 |
+
"start": "00:00:10.0",
|
| 75 |
+
"end": "00:01:00.0",
|
| 76 |
+
"noise": "-25dB",
|
| 77 |
+
"duration": 0.5,
|
| 78 |
+
"mono": false,
|
| 79 |
+
"webhook_url": "https://example.com/webhook",
|
| 80 |
+
"id": "unique-request-id"
|
| 81 |
+
}'
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
## 4. Response
|
| 85 |
+
|
| 86 |
+
### Success Response
|
| 87 |
+
|
| 88 |
+
The success response will be sent as a webhook to the specified `webhook_url`. The response format follows the general response structure defined in the main application context (`app.py`). Here's an example:
|
| 89 |
+
|
| 90 |
+
```json
|
| 91 |
+
{
|
| 92 |
+
"endpoint": "/v1/media/silence",
|
| 93 |
+
"code": 200,
|
| 94 |
+
"id": "unique-request-id",
|
| 95 |
+
"job_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
|
| 96 |
+
"response": [
|
| 97 |
+
{
|
| 98 |
+
"start": 10.5,
|
| 99 |
+
"end": 15.2
|
| 100 |
+
},
|
| 101 |
+
{
|
| 102 |
+
"start": 20.0,
|
| 103 |
+
"end": 25.7
|
| 104 |
+
}
|
| 105 |
+
],
|
| 106 |
+
"message": "success",
|
| 107 |
+
"pid": 12345,
|
| 108 |
+
"queue_id": 1234567890,
|
| 109 |
+
"run_time": 1.234,
|
| 110 |
+
"queue_time": 0.123,
|
| 111 |
+
"total_time": 1.357,
|
| 112 |
+
"queue_length": 0,
|
| 113 |
+
"build_number": "1.0.0"
|
| 114 |
+
}
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
### Error Responses
|
| 118 |
+
|
| 119 |
+
- **400 Bad Request**: This error is returned when the request body is missing or contains invalid parameters. Example response:
|
| 120 |
+
|
| 121 |
+
```json
|
| 122 |
+
{
|
| 123 |
+
"code": 400,
|
| 124 |
+
"message": "Invalid request payload"
|
| 125 |
+
}
|
| 126 |
+
```
|
| 127 |
+
|
| 128 |
+
- **401 Unauthorized**: This error is returned when the `x-api-key` header is missing or invalid. Example response:
|
| 129 |
+
|
| 130 |
+
```json
|
| 131 |
+
{
|
| 132 |
+
"code": 401,
|
| 133 |
+
"message": "Unauthorized"
|
| 134 |
+
}
|
| 135 |
+
```
|
| 136 |
+
|
| 137 |
+
- **500 Internal Server Error**: This error is returned when an unexpected error occurs during the silence detection process. Example response:
|
| 138 |
+
|
| 139 |
+
```json
|
| 140 |
+
{
|
| 141 |
+
"code": 500,
|
| 142 |
+
"message": "An error occurred during the silence detection process"
|
| 143 |
+
}
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
## 5. Error Handling
|
| 147 |
+
|
| 148 |
+
The endpoint handles the following common errors:
|
| 149 |
+
|
| 150 |
+
- Missing or invalid request parameters: Returns a 400 Bad Request error.
|
| 151 |
+
- Missing or invalid `x-api-key` header: Returns a 401 Unauthorized error.
|
| 152 |
+
- Unexpected exceptions during the silence detection process: Returns a 500 Internal Server Error.
|
| 153 |
+
|
| 154 |
+
The main application context (`app.py`) also includes error handling for situations where the task queue has reached its maximum length (`MAX_QUEUE_LENGTH`). In such cases, a 429 Too Many Requests error is returned.
|
| 155 |
+
|
| 156 |
+
## 6. Usage Notes
|
| 157 |
+
|
| 158 |
+
- The `media_url` parameter should point to a valid media file that can be processed by the silence detection service.
|
| 159 |
+
- The `start` and `end` parameters are optional and can be used to specify a time range within the media file for silence detection.
|
| 160 |
+
- The `noise` parameter allows you to adjust the noise threshold for silence detection. Lower values (e.g., `-40dB`) will detect more silence intervals, while higher values (e.g., `-20dB`) will detect fewer silence intervals.
|
| 161 |
+
- The `duration` parameter specifies the minimum duration (in seconds) for a silence interval to be considered valid. This can be useful for filtering out very short silence intervals that may not be relevant.
|
| 162 |
+
- The `mono` parameter determines whether the audio should be processed as a single channel (mono) or multiple channels (stereo or surround).
|
| 163 |
+
|
| 164 |
+
## 7. Common Issues
|
| 165 |
+
|
| 166 |
+
- Providing an invalid or inaccessible `media_url`.
|
| 167 |
+
- Specifying `start` and `end` times that are outside the duration of the media file.
|
| 168 |
+
- Setting the `duration` parameter to an unreasonably low value, which may result in detecting too many short silence intervals.
|
| 169 |
+
|
| 170 |
+
## 8. Best Practices
|
| 171 |
+
|
| 172 |
+
- Validate the `media_url` parameter to ensure it points to a valid and accessible media file.
|
| 173 |
+
- Consider using the `start` and `end` parameters to focus the silence detection on a specific time range within the media file, if needed.
|
| 174 |
+
- Adjust the `noise` and `duration` parameters based on your specific use case and requirements for silence detection.
|
| 175 |
+
- If you need to process stereo or surround audio, set the `mono` parameter to `false`.
|
| 176 |
+
- Monitor the response from the endpoint to ensure that the silence detection process completed successfully and that the detected silence intervals meet your expectations.
|
docs/s3/upload.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# S3 Upload API
|
| 2 |
+
|
| 3 |
+
This endpoint allows you to stream a file from a remote URL directly to an S3-compatible storage service without using local disk space.
|
| 4 |
+
|
| 5 |
+
## Endpoint
|
| 6 |
+
|
| 7 |
+
`POST /v1/s3/upload`
|
| 8 |
+
|
| 9 |
+
## Authentication
|
| 10 |
+
|
| 11 |
+
This endpoint requires an API key to be provided in the `X-API-Key` header.
|
| 12 |
+
|
| 13 |
+
## Request Body
|
| 14 |
+
|
| 15 |
+
The request body should be a JSON object with the following properties:
|
| 16 |
+
|
| 17 |
+
| Property | Type | Required | Description |
|
| 18 |
+
|----------|------|----------|-------------|
|
| 19 |
+
| file_url | string | Yes | The URL of the file to upload to S3 |
|
| 20 |
+
| filename | string | No | Custom filename to use for the uploaded file. If not provided, the original filename will be used |
|
| 21 |
+
| public | boolean | No | Whether to make the file publicly accessible. Defaults to `false` |
|
| 22 |
+
|
| 23 |
+
Example request body:
|
| 24 |
+
```json
|
| 25 |
+
{
|
| 26 |
+
"file_url": "https://example.com/path/to/file.mp4",
|
| 27 |
+
"filename": "custom-name.mp4",
|
| 28 |
+
"public": true
|
| 29 |
+
}
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
## Response
|
| 33 |
+
|
| 34 |
+
The response will be a JSON object with the following properties:
|
| 35 |
+
|
| 36 |
+
| Property | Type | Description |
|
| 37 |
+
|----------|------|-------------|
|
| 38 |
+
| url | string | The URL of the uploaded file. For public files, this is a direct URL. For private files, this is a pre-signed URL that will expire after 1 hour |
|
| 39 |
+
| filename | string | The filename of the uploaded file |
|
| 40 |
+
| bucket | string | The name of the S3 bucket where the file was uploaded |
|
| 41 |
+
| public | boolean | Whether the file is publicly accessible |
|
| 42 |
+
|
| 43 |
+
Example response:
|
| 44 |
+
```json
|
| 45 |
+
{
|
| 46 |
+
"url": "https://bucket-name.s3.region.amazonaws.com/custom-name.mp4",
|
| 47 |
+
"filename": "custom-name.mp4",
|
| 48 |
+
"bucket": "bucket-name",
|
| 49 |
+
"public": true
|
| 50 |
+
}
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
## Error Handling
|
| 54 |
+
|
| 55 |
+
If an error occurs, the response will include an error message with an appropriate HTTP status code.
|
| 56 |
+
|
| 57 |
+
## Technical Details
|
| 58 |
+
|
| 59 |
+
This endpoint uses the S3-compatible multipart upload API to stream the file directly from the source URL to S3 without saving it locally. This allows for efficient transfer of large files with minimal memory usage.
|
| 60 |
+
|
| 61 |
+
The implementation:
|
| 62 |
+
1. Streams the file from the source URL in chunks
|
| 63 |
+
2. Uploads each chunk to S3 as a part of a multipart upload
|
| 64 |
+
3. Completes the multipart upload once all parts are uploaded
|
| 65 |
+
|
| 66 |
+
This approach supports resumable uploads and can handle large files efficiently.
|
docs/toolkit/authenticate.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Authenticate Endpoint
|
| 2 |
+
|
| 3 |
+
## 1. Overview
|
| 4 |
+
|
| 5 |
+
The `/v1/toolkit/authenticate` endpoint is a part of the `v1_toolkit_auth` blueprint in the API structure. Its purpose is to authenticate requests by verifying the provided API key against a predefined value. This endpoint serves as a gatekeeper, ensuring that only authorized clients can access the API's resources.
|
| 6 |
+
|
| 7 |
+
## 2. Endpoint
|
| 8 |
+
|
| 9 |
+
- URL Path: `/v1/toolkit/authenticate`
|
| 10 |
+
- HTTP Method: `GET`
|
| 11 |
+
|
| 12 |
+
## 3. Request
|
| 13 |
+
|
| 14 |
+
### Headers
|
| 15 |
+
|
| 16 |
+
- `X-API-Key` (required): The API key used for authentication.
|
| 17 |
+
|
| 18 |
+
### Body Parameters
|
| 19 |
+
|
| 20 |
+
This endpoint does not require any request body parameters.
|
| 21 |
+
|
| 22 |
+
### Example Request
|
| 23 |
+
|
| 24 |
+
```bash
|
| 25 |
+
curl -X GET -H "X-API-Key: YOUR_API_KEY" http://localhost:8080/v1/toolkit/authenticate
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
## 4. Response
|
| 29 |
+
|
| 30 |
+
### Success Response
|
| 31 |
+
|
| 32 |
+
If the provided API key matches the predefined value, the endpoint will return a 200 OK status code with the following response:
|
| 33 |
+
|
| 34 |
+
```json
|
| 35 |
+
{
|
| 36 |
+
"code": 200,
|
| 37 |
+
"endpoint": "/authenticate",
|
| 38 |
+
"id": null,
|
| 39 |
+
"job_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
|
| 40 |
+
"message": "success",
|
| 41 |
+
"pid": 12345,
|
| 42 |
+
"queue_id": 1234567890,
|
| 43 |
+
"queue_length": 0,
|
| 44 |
+
"response": "Authorized",
|
| 45 |
+
"run_time": 0.001,
|
| 46 |
+
"total_time": 0.001,
|
| 47 |
+
"queue_time": 0,
|
| 48 |
+
"build_number": "1.0.0"
|
| 49 |
+
}
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
### Error Responses
|
| 53 |
+
|
| 54 |
+
If the provided API key is invalid or missing, the endpoint will return a 401 Unauthorized status code with the following response:
|
| 55 |
+
|
| 56 |
+
```json
|
| 57 |
+
{
|
| 58 |
+
"code": 401,
|
| 59 |
+
"endpoint": "/authenticate",
|
| 60 |
+
"id": null,
|
| 61 |
+
"job_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
|
| 62 |
+
"message": "Unauthorized",
|
| 63 |
+
"pid": 12345,
|
| 64 |
+
"queue_id": 1234567890,
|
| 65 |
+
"queue_length": 0,
|
| 66 |
+
"response": null,
|
| 67 |
+
"run_time": 0.001,
|
| 68 |
+
"total_time": 0.001,
|
| 69 |
+
"queue_time": 0,
|
| 70 |
+
"build_number": "1.0.0"
|
| 71 |
+
}
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
## 5. Error Handling
|
| 75 |
+
|
| 76 |
+
The main error that can occur with this endpoint is providing an invalid or missing API key. In this case, the endpoint will return a 401 Unauthorized status code with an appropriate error message.
|
| 77 |
+
|
| 78 |
+
## 6. Usage Notes
|
| 79 |
+
|
| 80 |
+
- This endpoint is designed to be used as a gatekeeper for the API, ensuring that only authorized clients can access the API's resources.
|
| 81 |
+
- The API key should be kept secure and should not be shared with unauthorized parties.
|
| 82 |
+
|
| 83 |
+
## 7. Common Issues
|
| 84 |
+
|
| 85 |
+
- Forgetting to include the `X-API-Key` header in the request.
|
| 86 |
+
- Using an invalid or expired API key.
|
| 87 |
+
|
| 88 |
+
## 8. Best Practices
|
| 89 |
+
|
| 90 |
+
- Rotate API keys periodically to enhance security.
|
| 91 |
+
- Store API keys securely and avoid committing them to version control systems.
|
| 92 |
+
- Consider implementing additional security measures, such as rate limiting or IP whitelisting, to further protect the API.
|
docs/toolkit/job_status.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Job Status Endpoint Documentation
|
| 2 |
+
|
| 3 |
+
## 1. Overview
|
| 4 |
+
|
| 5 |
+
The `/v1/toolkit/job/status` endpoint is part of the Toolkit API and is used to retrieve the status of a specific job. It fits into the overall API structure as a utility endpoint for monitoring and managing jobs submitted to the various media processing endpoints.
|
| 6 |
+
|
| 7 |
+
## 2. Endpoint
|
| 8 |
+
|
| 9 |
+
**URL Path:** `/v1/toolkit/job/status`
|
| 10 |
+
**HTTP Method:** `POST`
|
| 11 |
+
|
| 12 |
+
## 3. Request
|
| 13 |
+
|
| 14 |
+
### Headers
|
| 15 |
+
|
| 16 |
+
- `x-api-key` (required): The API key for authentication.
|
| 17 |
+
|
| 18 |
+
### Body Parameters
|
| 19 |
+
|
| 20 |
+
The request body must be a JSON object with the following parameter:
|
| 21 |
+
|
| 22 |
+
- `job_id` (string, required): The unique identifier of the job for which the status is requested.
|
| 23 |
+
|
| 24 |
+
The `validate_payload` directive in the routes file enforces the following JSON schema for the request body:
|
| 25 |
+
|
| 26 |
+
```python
|
| 27 |
+
{
|
| 28 |
+
"type": "object",
|
| 29 |
+
"properties": {
|
| 30 |
+
"job_id": {
|
| 31 |
+
"type": "string"
|
| 32 |
+
}
|
| 33 |
+
},
|
| 34 |
+
"required": ["job_id"],
|
| 35 |
+
}
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
### Example Request
|
| 39 |
+
|
| 40 |
+
```json
|
| 41 |
+
{
|
| 42 |
+
"job_id": "e6d7f3c0-9c9f-4b8a-b7c3-f0e3c9f6b9d7"
|
| 43 |
+
}
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
```bash
|
| 47 |
+
curl -X POST \
|
| 48 |
+
-H "x-api-key: YOUR_API_KEY" \
|
| 49 |
+
-H "Content-Type: application/json" \
|
| 50 |
+
-d '{"job_id": "e6d7f3c0-9c9f-4b8a-b7c3-f0e3c9f6b9d7"}' \
|
| 51 |
+
http://your-api-endpoint/v1/toolkit/job/status
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
## 4. Response
|
| 55 |
+
|
| 56 |
+
### Success Response
|
| 57 |
+
|
| 58 |
+
The success response will contain the job status file content directly, as shown in the example response from `app.py`:
|
| 59 |
+
|
| 60 |
+
```json
|
| 61 |
+
{
|
| 62 |
+
"endpoint": "/v1/toolkit/job/status",
|
| 63 |
+
"code": 200,
|
| 64 |
+
"id": null,
|
| 65 |
+
"job_id": "e6d7f3c0-9c9f-4b8a-b7c3-f0e3c9f6b9d7",
|
| 66 |
+
"response": {
|
| 67 |
+
"job_status": "done",
|
| 68 |
+
"job_id": "e6d7f3c0-9c9f-4b8a-b7c3-f0e3c9f6b9d7",
|
| 69 |
+
"queue_id": 140368864456064,
|
| 70 |
+
"process_id": 123456,
|
| 71 |
+
"response": {
|
| 72 |
+
"endpoint": "/v1/media/transcribe",
|
| 73 |
+
"code": 200,
|
| 74 |
+
"id": "transcribe_job_123",
|
| 75 |
+
"job_id": "e6d7f3c0-9c9f-4b8a-b7c3-f0e3c9f6b9d7",
|
| 76 |
+
"response": "Transcription completed successfully.",
|
| 77 |
+
"message": "success",
|
| 78 |
+
"pid": 123456,
|
| 79 |
+
"queue_id": 140368864456064,
|
| 80 |
+
"run_time": 5.234,
|
| 81 |
+
"queue_time": 1.123,
|
| 82 |
+
"total_time": 6.357,
|
| 83 |
+
"queue_length": 0,
|
| 84 |
+
"build_number": "1.0.0"
|
| 85 |
+
}
|
| 86 |
+
},
|
| 87 |
+
"message": "success",
|
| 88 |
+
"pid": 123456,
|
| 89 |
+
"queue_id": 140368864456064,
|
| 90 |
+
"run_time": 0.001,
|
| 91 |
+
"queue_time": 0.0,
|
| 92 |
+
"total_time": 0.001,
|
| 93 |
+
"queue_length": 0,
|
| 94 |
+
"build_number": "1.0.0"
|
| 95 |
+
}
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
### Error Responses
|
| 99 |
+
|
| 100 |
+
- **404 Not Found**: If the job with the provided `job_id` is not found, the response will be:
|
| 101 |
+
|
| 102 |
+
```json
|
| 103 |
+
{
|
| 104 |
+
"error": "Job not found",
|
| 105 |
+
"job_id": "e6d7f3c0-9c9f-4b8a-b7c3-f0e3c9f6b9d7"
|
| 106 |
+
}
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
- **500 Internal Server Error**: If an unexpected error occurs while retrieving the job status, the response will be:
|
| 110 |
+
|
| 111 |
+
```json
|
| 112 |
+
{
|
| 113 |
+
"error": "Failed to retrieve job status: <error_message>",
|
| 114 |
+
"code": 500
|
| 115 |
+
}
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
## 5. Error Handling
|
| 119 |
+
|
| 120 |
+
- **Missing or Invalid Parameters**: If the `job_id` parameter is missing or invalid, the request will be rejected with a 400 Bad Request error.
|
| 121 |
+
- **Job Not Found**: If the job with the provided `job_id` is not found, a 404 Not Found error will be returned.
|
| 122 |
+
- **Unexpected Errors**: Any unexpected errors that occur during the retrieval of the job status will result in a 500 Internal Server Error response.
|
| 123 |
+
|
| 124 |
+
The main application context (`app.py`) includes error handling for queue overflow situations, where a 429 Too Many Requests error is returned if the maximum queue length is reached.
|
| 125 |
+
|
| 126 |
+
## 6. Usage Notes
|
| 127 |
+
|
| 128 |
+
- Ensure that you have a valid API key for authentication.
|
| 129 |
+
- The `job_id` parameter must be a valid UUID string representing an existing job.
|
| 130 |
+
- This endpoint does not perform any media processing; it only retrieves the status of a previously submitted job.
|
| 131 |
+
|
| 132 |
+
## 7. Common Issues
|
| 133 |
+
|
| 134 |
+
- Providing an invalid or non-existent `job_id`.
|
| 135 |
+
- Attempting to retrieve the status of a job that has already been processed and removed from the system.
|
| 136 |
+
|
| 137 |
+
## 8. Best Practices
|
| 138 |
+
|
| 139 |
+
- Use this endpoint to monitor the progress of long-running jobs or to check the status of completed jobs.
|
| 140 |
+
- Implement proper error handling in your client application to handle different error scenarios, such as job not found or unexpected errors.
|
| 141 |
+
- Consider rate-limiting or implementing a queue system on the client side to avoid overwhelming the API with too many requests.
|
docs/toolkit/jobs_status.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Get All Jobs Status
|
| 2 |
+
|
| 3 |
+
## 1. Overview
|
| 4 |
+
|
| 5 |
+
The `/v1/toolkit/jobs/status` endpoint is part of the Toolkit API and is used to retrieve the status of all jobs within a specified time range. This endpoint fits into the overall API structure by providing a way to monitor and manage the jobs that have been submitted to the system. It is a part of the `v1_toolkit_jobs_status_bp` blueprint, which is registered in the main `app.py` file.
|
| 6 |
+
|
| 7 |
+
## 2. Endpoint
|
| 8 |
+
|
| 9 |
+
**URL Path:** `/v1/toolkit/jobs/status`
|
| 10 |
+
**HTTP Method:** `POST`
|
| 11 |
+
|
| 12 |
+
## 3. Request
|
| 13 |
+
|
| 14 |
+
### Headers
|
| 15 |
+
|
| 16 |
+
- `x-api-key` (required): The API key for authentication.
|
| 17 |
+
|
| 18 |
+
### Body Parameters
|
| 19 |
+
|
| 20 |
+
- `since_seconds` (optional, number): The number of seconds to look back for jobs. If not provided, the default value is 600 seconds (10 minutes).
|
| 21 |
+
|
| 22 |
+
The JSON payload is completely optional. If no payload is provided or if the payload is empty, the endpoint will use the default value of 600 seconds.
|
| 23 |
+
|
| 24 |
+
### Example Request
|
| 25 |
+
|
| 26 |
+
```json
|
| 27 |
+
{
|
| 28 |
+
"since_seconds": 3600
|
| 29 |
+
}
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
Or with no body:
|
| 33 |
+
|
| 34 |
+
```bash
|
| 35 |
+
curl -X POST \
|
| 36 |
+
-H "x-api-key: YOUR_API_KEY" \
|
| 37 |
+
-H "Content-Type: application/json" \
|
| 38 |
+
http://your-api-url/v1/toolkit/jobs/status
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
With body:
|
| 42 |
+
|
| 43 |
+
```bash
|
| 44 |
+
curl -X POST \
|
| 45 |
+
-H "x-api-key: YOUR_API_KEY" \
|
| 46 |
+
-H "Content-Type: application/json" \
|
| 47 |
+
-d '{"since_seconds": 3600}' \
|
| 48 |
+
http://your-api-url/v1/toolkit/jobs/status
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
## 4. Response
|
| 52 |
+
|
| 53 |
+
### Success Response
|
| 54 |
+
|
| 55 |
+
The response will be a JSON object containing the job statuses for all jobs within the specified time range. The response format follows the general response structure defined in `app.py`.
|
| 56 |
+
|
| 57 |
+
```json
|
| 58 |
+
{
|
| 59 |
+
"code": 200,
|
| 60 |
+
"id": null,
|
| 61 |
+
"job_id": "job_id_value",
|
| 62 |
+
"response": {
|
| 63 |
+
"job_id_1": "job_status_1",
|
| 64 |
+
"job_id_2": "job_status_2",
|
| 65 |
+
...
|
| 66 |
+
},
|
| 67 |
+
"message": "success",
|
| 68 |
+
"run_time": 0.123,
|
| 69 |
+
"queue_time": 0,
|
| 70 |
+
"total_time": 0.123,
|
| 71 |
+
"pid": 12345,
|
| 72 |
+
"queue_id": 1234567890,
|
| 73 |
+
"queue_length": 0,
|
| 74 |
+
"build_number": "1.0.0"
|
| 75 |
+
}
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
### Error Responses
|
| 79 |
+
|
| 80 |
+
- **404 Not Found**: If the jobs directory is not found.
|
| 81 |
+
|
| 82 |
+
```json
|
| 83 |
+
{
|
| 84 |
+
"code": 404,
|
| 85 |
+
"id": null,
|
| 86 |
+
"job_id": "job_id_value",
|
| 87 |
+
"response": null,
|
| 88 |
+
"message": "Jobs directory not found",
|
| 89 |
+
"run_time": 0.123,
|
| 90 |
+
"queue_time": 0,
|
| 91 |
+
"total_time": 0.123,
|
| 92 |
+
"pid": 12345,
|
| 93 |
+
"queue_id": 1234567890,
|
| 94 |
+
"queue_length": 0,
|
| 95 |
+
"build_number": "1.0.0"
|
| 96 |
+
}
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
- **500 Internal Server Error**: If an exception occurs while retrieving the job statuses.
|
| 100 |
+
|
| 101 |
+
```json
|
| 102 |
+
{
|
| 103 |
+
"code": 500,
|
| 104 |
+
"id": null,
|
| 105 |
+
"job_id": "job_id_value",
|
| 106 |
+
"response": null,
|
| 107 |
+
"message": "Failed to retrieve job statuses: Error message",
|
| 108 |
+
"run_time": 0.123,
|
| 109 |
+
"queue_time": 0,
|
| 110 |
+
"total_time": 0.123,
|
| 111 |
+
"pid": 12345,
|
| 112 |
+
"queue_id": 1234567890,
|
| 113 |
+
"queue_length": 0,
|
| 114 |
+
"build_number": "1.0.0"
|
| 115 |
+
}
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
## 5. Error Handling
|
| 119 |
+
|
| 120 |
+
- Missing or invalid `x-api-key` header: The `authenticate` decorator will return a 401 Unauthorized error.
|
| 121 |
+
- Jobs directory not found: The endpoint will return a 404 Not Found error if the jobs directory is not found.
|
| 122 |
+
- Exception during job status retrieval: The endpoint will return a 500 Internal Server Error if an exception occurs while retrieving the job statuses.
|
| 123 |
+
|
| 124 |
+
The main `app.py` file includes error handling for queue overflow (429 Too Many Requests) and logging of job statuses (queued, running, done) using the `log_job_status` function.
|
| 125 |
+
|
| 126 |
+
## 6. Usage Notes
|
| 127 |
+
|
| 128 |
+
- This endpoint is useful for monitoring the status of jobs submitted to the system, especially when dealing with long-running or queued jobs.
|
| 129 |
+
- The `since_seconds` parameter can be adjusted to retrieve job statuses within a specific time range, allowing for more targeted monitoring.
|
| 130 |
+
|
| 131 |
+
## 7. Common Issues
|
| 132 |
+
|
| 133 |
+
- Providing an invalid `x-api-key` header will result in an authentication error.
|
| 134 |
+
- If the jobs directory is not found or an exception occurs during job status retrieval, the endpoint will return an error.
|
| 135 |
+
|
| 136 |
+
## 8. Best Practices
|
| 137 |
+
|
| 138 |
+
- Always include the `x-api-key` header with a valid API key for authentication.
|
| 139 |
+
- Monitor the job statuses regularly to keep track of the progress and completion of submitted jobs.
|
| 140 |
+
- Adjust the `since_seconds` parameter based on your monitoring requirements to retrieve job statuses within a specific time range.
|
| 141 |
+
- Implement error handling and logging mechanisms to track and troubleshoot any issues that may arise.
|
docs/toolkit/test.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# NCA Toolkit Test API Endpoint
|
| 2 |
+
|
| 3 |
+
## 1. Overview
|
| 4 |
+
|
| 5 |
+
The `/v1/toolkit/test` endpoint is a part of the NCA Toolkit API and is designed to test the API setup. It creates a temporary file, uploads it to cloud storage, and then returns the upload URL. This endpoint serves as a simple test to ensure that the API is properly configured and can perform basic file operations and cloud storage interactions.
|
| 6 |
+
|
| 7 |
+
## 2. Endpoint
|
| 8 |
+
|
| 9 |
+
**URL Path:** `/v1/toolkit/test`
|
| 10 |
+
**HTTP Method:** `GET`
|
| 11 |
+
|
| 12 |
+
## 3. Request
|
| 13 |
+
|
| 14 |
+
### Headers
|
| 15 |
+
|
| 16 |
+
- `x-api-key` (required): The API key for authentication.
|
| 17 |
+
|
| 18 |
+
### Body Parameters
|
| 19 |
+
|
| 20 |
+
This endpoint does not require any request body parameters.
|
| 21 |
+
|
| 22 |
+
### Example Request
|
| 23 |
+
|
| 24 |
+
```bash
|
| 25 |
+
curl -X GET \
|
| 26 |
+
https://your-api-url.com/v1/toolkit/test \
|
| 27 |
+
-H 'x-api-key: your-api-key'
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
## 4. Response
|
| 31 |
+
|
| 32 |
+
### Success Response
|
| 33 |
+
|
| 34 |
+
```json
|
| 35 |
+
{
|
| 36 |
+
"endpoint": "/v1/toolkit/test",
|
| 37 |
+
"code": 200,
|
| 38 |
+
"id": null,
|
| 39 |
+
"job_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
|
| 40 |
+
"response": "https://cloud-storage.com/success.txt",
|
| 41 |
+
"message": "success",
|
| 42 |
+
"pid": 12345,
|
| 43 |
+
"queue_id": 67890,
|
| 44 |
+
"run_time": 0.123,
|
| 45 |
+
"queue_time": 0.0,
|
| 46 |
+
"total_time": 0.123,
|
| 47 |
+
"queue_length": 0,
|
| 48 |
+
"build_number": "1.0.0"
|
| 49 |
+
}
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
### Error Responses
|
| 53 |
+
|
| 54 |
+
**Status Code: 401 Unauthorized**
|
| 55 |
+
|
| 56 |
+
```json
|
| 57 |
+
{
|
| 58 |
+
"code": 401,
|
| 59 |
+
"message": "Unauthorized: Invalid or missing API key"
|
| 60 |
+
}
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
**Status Code: 500 Internal Server Error**
|
| 64 |
+
|
| 65 |
+
```json
|
| 66 |
+
{
|
| 67 |
+
"code": 500,
|
| 68 |
+
"message": "An error occurred while processing the request"
|
| 69 |
+
}
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
## 5. Error Handling
|
| 73 |
+
|
| 74 |
+
- **Missing or Invalid API Key (401 Unauthorized)**: If the `x-api-key` header is missing or invalid, the API will return a 401 Unauthorized error.
|
| 75 |
+
- **Internal Server Error (500)**: If an unexpected error occurs during the file creation, upload, or any other operation, the API will return a 500 Internal Server Error with the error message.
|
| 76 |
+
|
| 77 |
+
## 6. Usage Notes
|
| 78 |
+
|
| 79 |
+
This endpoint is primarily used for testing purposes and does not require any specific input parameters. It can be called to verify that the API is set up correctly and can perform basic operations.
|
| 80 |
+
|
| 81 |
+
## 7. Common Issues
|
| 82 |
+
|
| 83 |
+
- Incorrect or missing API key: Ensure that the `x-api-key` header is included with a valid API key.
|
| 84 |
+
- Temporary file creation or upload issues: If there are any issues with creating or uploading the temporary file, the API will return an error.
|
| 85 |
+
|
| 86 |
+
## 8. Best Practices
|
| 87 |
+
|
| 88 |
+
- Use this endpoint during the initial setup and testing phase of the API integration to ensure that the API is configured correctly.
|
| 89 |
+
- Regularly test the API setup using this endpoint to catch any potential issues or configuration changes that may affect the API's functionality.
|
docs/video/caption_video.md
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Video Captioning Endpoint (v1)
|
| 2 |
+
|
| 3 |
+
## 1. Overview
|
| 4 |
+
|
| 5 |
+
The `/v1/video/caption` endpoint is part of the Video API and is responsible for adding captions to a video file. It accepts a video URL, caption text, and various styling options for the captions. The endpoint utilizes the `process_captioning_v1` service to generate a captioned video file, which is then uploaded to cloud storage, and the cloud URL is returned in the response.
|
| 6 |
+
|
| 7 |
+
## 2. Endpoint
|
| 8 |
+
|
| 9 |
+
**URL:** `/v1/video/caption`
|
| 10 |
+
**Method:** `POST`
|
| 11 |
+
|
| 12 |
+
## 3. Request
|
| 13 |
+
|
| 14 |
+
### Headers
|
| 15 |
+
|
| 16 |
+
- `x-api-key`: Required. The API key for authentication.
|
| 17 |
+
|
| 18 |
+
### Body Parameters
|
| 19 |
+
|
| 20 |
+
The request body must be a JSON object with the following properties:
|
| 21 |
+
|
| 22 |
+
- `video_url` (string, required): The URL of the video file to be captioned.
|
| 23 |
+
- `captions` (string, optional): Can be one of the following:
|
| 24 |
+
- Raw caption text to be added to the video
|
| 25 |
+
- URL to an SRT subtitle file
|
| 26 |
+
- URL to an ASS subtitle file
|
| 27 |
+
- If not provided, the system will automatically generate captions by transcribing the audio from the video
|
| 28 |
+
- `settings` (object, optional): An object containing various styling options for the captions. See the schema below for available options.
|
| 29 |
+
- `replace` (array, optional): An array of objects with `find` and `replace` properties, specifying text replacements to be made in the captions.
|
| 30 |
+
- `webhook_url` (string, optional): A URL to receive a webhook notification when the captioning process is complete.
|
| 31 |
+
- `id` (string, optional): An identifier for the request.
|
| 32 |
+
- `language` (string, optional): The language code for the captions (e.g., "en", "fr"). Defaults to "auto".
|
| 33 |
+
- `exclude_time_ranges` (array, optional): List of time ranges to skip when adding captions. Each item must be an object with:
|
| 34 |
+
- `start`: (string, required) The start time of the excluded range, as a string timecode in `hh:mm:ss.ms` format (e.g., `00:01:23.456`).
|
| 35 |
+
- `end`: (string, required) The end time, as a string timecode in `hh:mm:ss.ms` format, which must be strictly greater than `start`.
|
| 36 |
+
If either value is not a valid timecode string, or if `end` is not greater than `start`, the request will return an error.
|
| 37 |
+
|
| 38 |
+
#### Settings Schema
|
| 39 |
+
|
| 40 |
+
```json
|
| 41 |
+
{
|
| 42 |
+
"type": "object",
|
| 43 |
+
"properties": {
|
| 44 |
+
"line_color": {"type": "string"},
|
| 45 |
+
"word_color": {"type": "string"},
|
| 46 |
+
"outline_color": {"type": "string"},
|
| 47 |
+
"all_caps": {"type": "boolean"},
|
| 48 |
+
"max_words_per_line": {"type": "integer"},
|
| 49 |
+
"x": {"type": "integer"},
|
| 50 |
+
"y": {"type": "integer"},
|
| 51 |
+
"position": {
|
| 52 |
+
"type": "string",
|
| 53 |
+
"enum": [
|
| 54 |
+
"bottom_left", "bottom_center", "bottom_right",
|
| 55 |
+
"middle_left", "middle_center", "middle_right",
|
| 56 |
+
"top_left", "top_center", "top_right"
|
| 57 |
+
]
|
| 58 |
+
},
|
| 59 |
+
"alignment": {
|
| 60 |
+
"type": "string",
|
| 61 |
+
"enum": ["left", "center", "right"]
|
| 62 |
+
},
|
| 63 |
+
"font_family": {"type": "string"},
|
| 64 |
+
"font_size": {"type": "integer"},
|
| 65 |
+
"bold": {"type": "boolean"},
|
| 66 |
+
"italic": {"type": "boolean"},
|
| 67 |
+
"underline": {"type": "boolean"},
|
| 68 |
+
"strikeout": {"type": "boolean"},
|
| 69 |
+
"style": {
|
| 70 |
+
"type": "string",
|
| 71 |
+
"enum": [
|
| 72 |
+
"classic", // Regular captioning with all text displayed at once
|
| 73 |
+
"karaoke", // Highlights words sequentially in a karaoke style
|
| 74 |
+
"highlight", // Shows full text but highlights the current word
|
| 75 |
+
"underline", // Shows full text but underlines the current word
|
| 76 |
+
"word_by_word" // Shows one word at a time
|
| 77 |
+
]
|
| 78 |
+
},
|
| 79 |
+
"outline_width": {"type": "integer"},
|
| 80 |
+
"spacing": {"type": "integer"},
|
| 81 |
+
"angle": {"type": "integer"},
|
| 82 |
+
"shadow_offset": {"type": "integer"}
|
| 83 |
+
},
|
| 84 |
+
"additionalProperties": false
|
| 85 |
+
}
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
### Example Requests
|
| 89 |
+
|
| 90 |
+
#### Example 1: Basic Automatic Captioning
|
| 91 |
+
```json
|
| 92 |
+
{
|
| 93 |
+
"video_url": "https://example.com/video.mp4"
|
| 94 |
+
}
|
| 95 |
+
```
|
| 96 |
+
This minimal request will automatically transcribe the video and add white captions at the bottom center.
|
| 97 |
+
|
| 98 |
+
#### Example 2: Custom Text with Styling
|
| 99 |
+
```json
|
| 100 |
+
{
|
| 101 |
+
"video_url": "https://example.com/video.mp4",
|
| 102 |
+
"captions": "This is a sample caption text.",
|
| 103 |
+
"settings": {
|
| 104 |
+
"style": "classic",
|
| 105 |
+
"line_color": "#FFFFFF",
|
| 106 |
+
"outline_color": "#000000",
|
| 107 |
+
"position": "bottom_center",
|
| 108 |
+
"alignment": "center",
|
| 109 |
+
"font_family": "Arial",
|
| 110 |
+
"font_size": 24,
|
| 111 |
+
"bold": true
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
#### Example 3: Karaoke-Style Captions with Advanced Options
|
| 117 |
+
```json
|
| 118 |
+
{
|
| 119 |
+
"video_url": "https://example.com/video.mp4",
|
| 120 |
+
"settings": {
|
| 121 |
+
"line_color": "#FFFFFF",
|
| 122 |
+
"word_color": "#FFFF00",
|
| 123 |
+
"outline_color": "#000000",
|
| 124 |
+
"all_caps": false,
|
| 125 |
+
"max_words_per_line": 10,
|
| 126 |
+
"position": "bottom_center",
|
| 127 |
+
"alignment": "center",
|
| 128 |
+
"font_family": "Arial",
|
| 129 |
+
"font_size": 24,
|
| 130 |
+
"bold": false,
|
| 131 |
+
"italic": false,
|
| 132 |
+
"style": "karaoke",
|
| 133 |
+
"outline_width": 2,
|
| 134 |
+
"shadow_offset": 2
|
| 135 |
+
},
|
| 136 |
+
"replace": [
|
| 137 |
+
{
|
| 138 |
+
"find": "um",
|
| 139 |
+
"replace": ""
|
| 140 |
+
},
|
| 141 |
+
{
|
| 142 |
+
"find": "like",
|
| 143 |
+
"replace": ""
|
| 144 |
+
}
|
| 145 |
+
],
|
| 146 |
+
"webhook_url": "https://example.com/webhook",
|
| 147 |
+
"id": "request-123",
|
| 148 |
+
"language": "en"
|
| 149 |
+
}
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
#### Example 4: Using an External Subtitle File
|
| 153 |
+
```json
|
| 154 |
+
{
|
| 155 |
+
"video_url": "https://example.com/video.mp4",
|
| 156 |
+
"captions": "https://example.com/subtitles.srt",
|
| 157 |
+
"settings": {
|
| 158 |
+
"line_color": "#FFFFFF",
|
| 159 |
+
"outline_color": "#000000",
|
| 160 |
+
"position": "bottom_center",
|
| 161 |
+
"font_family": "Arial",
|
| 162 |
+
"font_size": 24
|
| 163 |
+
}
|
| 164 |
+
}
|
| 165 |
+
```
|
| 166 |
+
|
| 167 |
+
#### Example 5: Excluding Time Ranges from Captioning
|
| 168 |
+
```json
|
| 169 |
+
{
|
| 170 |
+
"video_url": "https://example.com/video.mp4",
|
| 171 |
+
"settings": {
|
| 172 |
+
"style": "classic",
|
| 173 |
+
"line_color": "#FFFFFF",
|
| 174 |
+
"outline_color": "#000000",
|
| 175 |
+
"position": "bottom_center",
|
| 176 |
+
"font_family": "Arial",
|
| 177 |
+
"font_size": 24
|
| 178 |
+
},
|
| 179 |
+
"exclude_time_ranges": [
|
| 180 |
+
{ "start": "00:00:10.000", "end": "00:00:20.000" },
|
| 181 |
+
{ "start": "00:00:30.000", "end": "00:00:40.000" }
|
| 182 |
+
]
|
| 183 |
+
}
|
| 184 |
+
```
|
| 185 |
+
|
| 186 |
+
```bash
|
| 187 |
+
curl -X POST \
|
| 188 |
+
-H "x-api-key: YOUR_API_KEY" \
|
| 189 |
+
-H "Content-Type: application/json" \
|
| 190 |
+
-d '{
|
| 191 |
+
"video_url": "https://example.com/video.mp4",
|
| 192 |
+
"settings": {
|
| 193 |
+
"line_color": "#FFFFFF",
|
| 194 |
+
"word_color": "#FFFF00",
|
| 195 |
+
"outline_color": "#000000",
|
| 196 |
+
"all_caps": false,
|
| 197 |
+
"max_words_per_line": 10,
|
| 198 |
+
"position": "bottom_center",
|
| 199 |
+
"alignment": "center",
|
| 200 |
+
"font_family": "Arial",
|
| 201 |
+
"font_size": 24,
|
| 202 |
+
"style": "karaoke",
|
| 203 |
+
"outline_width": 2
|
| 204 |
+
},
|
| 205 |
+
"replace": [
|
| 206 |
+
{
|
| 207 |
+
"find": "um",
|
| 208 |
+
"replace": ""
|
| 209 |
+
}
|
| 210 |
+
],
|
| 211 |
+
"id": "custom-request-id"
|
| 212 |
+
}' \
|
| 213 |
+
https://your-api-endpoint.com/v1/video/caption
|
| 214 |
+
```
|
| 215 |
+
|
| 216 |
+
## 4. Response
|
| 217 |
+
|
| 218 |
+
### Success Response
|
| 219 |
+
|
| 220 |
+
The response will be a JSON object with the following properties:
|
| 221 |
+
|
| 222 |
+
- `code` (integer): The HTTP status code (200 for success).
|
| 223 |
+
- `id` (string): The request identifier, if provided in the request.
|
| 224 |
+
- `job_id` (string): A unique identifier for the job.
|
| 225 |
+
- `response` (string): The cloud URL of the captioned video file.
|
| 226 |
+
- `message` (string): A success message.
|
| 227 |
+
- `pid` (integer): The process ID of the worker that processed the request.
|
| 228 |
+
- `queue_id` (integer): The ID of the queue used for processing the request.
|
| 229 |
+
- `run_time` (float): The time taken to process the request (in seconds).
|
| 230 |
+
- `queue_time` (float): The time the request spent in the queue (in seconds).
|
| 231 |
+
- `total_time` (float): The total time taken for the request (in seconds).
|
| 232 |
+
- `queue_length` (integer): The current length of the processing queue.
|
| 233 |
+
- `build_number` (string): The build number of the application.
|
| 234 |
+
|
| 235 |
+
Example:
|
| 236 |
+
|
| 237 |
+
```json
|
| 238 |
+
{
|
| 239 |
+
"code": 200,
|
| 240 |
+
"id": "request-123",
|
| 241 |
+
"job_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
|
| 242 |
+
"response": "https://cloud.example.com/captioned-video.mp4",
|
| 243 |
+
"message": "success",
|
| 244 |
+
"pid": 12345,
|
| 245 |
+
"queue_id": 140682639937472,
|
| 246 |
+
"run_time": 5.234,
|
| 247 |
+
"queue_time": 0.012,
|
| 248 |
+
"total_time": 5.246,
|
| 249 |
+
"queue_length": 0,
|
| 250 |
+
"build_number": "1.0.0"
|
| 251 |
+
}
|
| 252 |
+
```
|
| 253 |
+
|
| 254 |
+
### Error Responses
|
| 255 |
+
|
| 256 |
+
#### Missing or Invalid Parameters
|
| 257 |
+
|
| 258 |
+
**Status Code:** 400 Bad Request
|
| 259 |
+
|
| 260 |
+
```json
|
| 261 |
+
{
|
| 262 |
+
"code": 400,
|
| 263 |
+
"id": "request-123",
|
| 264 |
+
"job_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
|
| 265 |
+
"message": "Missing or invalid parameters",
|
| 266 |
+
"pid": 12345,
|
| 267 |
+
"queue_id": 140682639937472,
|
| 268 |
+
"queue_length": 0,
|
| 269 |
+
"build_number": "1.0.0"
|
| 270 |
+
}
|
| 271 |
+
```
|
| 272 |
+
|
| 273 |
+
#### Font Error
|
| 274 |
+
|
| 275 |
+
**Status Code:** 400 Bad Request
|
| 276 |
+
|
| 277 |
+
```json
|
| 278 |
+
{
|
| 279 |
+
"code": 400,
|
| 280 |
+
"error": "The requested font 'InvalidFont' is not available. Please choose from the available fonts.",
|
| 281 |
+
"available_fonts": ["Arial", "Times New Roman", "Courier New", ...],
|
| 282 |
+
"pid": 12345,
|
| 283 |
+
"queue_id": 140682639937472,
|
| 284 |
+
"queue_length": 0,
|
| 285 |
+
"build_number": "1.0.0"
|
| 286 |
+
}
|
| 287 |
+
```
|
| 288 |
+
|
| 289 |
+
#### Internal Server Error
|
| 290 |
+
|
| 291 |
+
**Status Code:** 500 Internal Server Error
|
| 292 |
+
|
| 293 |
+
```json
|
| 294 |
+
{
|
| 295 |
+
"code": 500,
|
| 296 |
+
"id": "request-123",
|
| 297 |
+
"job_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
|
| 298 |
+
"error": "An unexpected error occurred during the captioning process.",
|
| 299 |
+
"pid": 12345,
|
| 300 |
+
"queue_id": 140682639937472,
|
| 301 |
+
"queue_length": 0,
|
| 302 |
+
"build_number": "1.0.0"
|
| 303 |
+
}
|
| 304 |
+
```
|
| 305 |
+
|
| 306 |
+
## 5. Error Handling
|
| 307 |
+
|
| 308 |
+
The endpoint handles the following common errors:
|
| 309 |
+
|
| 310 |
+
- **Missing or Invalid Parameters**: If any required parameters are missing or invalid, a 400 Bad Request error is returned with a descriptive error message.
|
| 311 |
+
- **Font Error**: If the requested font is not available, a 400 Bad Request error is returned with a list of available fonts.
|
| 312 |
+
- **Internal Server Error**: If an unexpected error occurs during the captioning process, a 500 Internal Server Error is returned with an error message.
|
| 313 |
+
|
| 314 |
+
Additionally, the main application context (`app.py`) includes error handling for queue overload. If the maximum queue length (`MAX_QUEUE_LENGTH`) is set and the queue size reaches that limit, a 429 Too Many Requests error is returned with a descriptive message.
|
| 315 |
+
|
| 316 |
+
## 6. Usage Notes
|
| 317 |
+
|
| 318 |
+
- The `video_url` parameter must be a valid URL pointing to a video file (MP4, MOV, etc.).
|
| 319 |
+
- The `captions` parameter is optional and can be used in multiple ways:
|
| 320 |
+
- If not provided, the endpoint will automatically transcribe the audio and generate captions
|
| 321 |
+
- If provided as plain text, the text will be used as captions for the entire video
|
| 322 |
+
- If provided as a URL to an SRT or ASS subtitle file, the system will use that file for captioning
|
| 323 |
+
- For SRT files, only 'classic' style is supported
|
| 324 |
+
- For ASS files, the original styling will be preserved
|
| 325 |
+
- The `settings` parameter allows for customization of the caption appearance and behavior:
|
| 326 |
+
- `style` determines how captions are displayed, with options including:
|
| 327 |
+
- `classic`: Regular captioning with all text displayed at once
|
| 328 |
+
- `karaoke`: Highlights words sequentially in a karaoke style as they're spoken
|
| 329 |
+
- `highlight`: Shows the full caption text but highlights each word as it's spoken
|
| 330 |
+
- `underline`: Shows the full caption text but underlines each word as it's spoken
|
| 331 |
+
- `word_by_word`: Shows only one word at a time
|
| 332 |
+
- `position` can be used to place captions in one of nine positions on the screen
|
| 333 |
+
- `alignment` determines text alignment within the position (left, center, right)
|
| 334 |
+
- `font_family` can be any available system font
|
| 335 |
+
- Color options can be set using hex codes (e.g., "#FFFFFF" for white)
|
| 336 |
+
- The `replace` parameter can be used to perform text replacements in the captions (useful for correcting words or censoring content).
|
| 337 |
+
- The `webhook_url` parameter is optional and can be used to receive a notification when the captioning process is complete.
|
| 338 |
+
- The `id` parameter is optional and can be used to identify the request in webhook responses.
|
| 339 |
+
- The `language` parameter is optional and can be used to specify the language of the captions for transcription. If not provided, the language will be automatically detected.
|
| 340 |
+
- The `exclude_time_ranges` parameter can be used to specify time ranges to be excluded from captioning.
|
| 341 |
+
|
| 342 |
+
## 7. Common Issues
|
| 343 |
+
|
| 344 |
+
- Providing an invalid or inaccessible `video_url`.
|
| 345 |
+
- Requesting an unavailable font in the `settings` object.
|
| 346 |
+
- Exceeding the maximum queue length, resulting in a 429 Too Many Requests error.
|
| 347 |
+
|
| 348 |
+
## 8. Best Practices
|
| 349 |
+
|
| 350 |
+
- Validate the `video_url` parameter before sending the request to ensure it points to a valid and accessible video file.
|
| 351 |
+
- Use the `webhook_url` parameter to receive notifications about the captioning process, rather than polling the API for updates.
|
| 352 |
+
- Provide descriptive and meaningful `id` values to easily identify requests in logs and responses.
|
| 353 |
+
- Use the `replace` parameter judiciously to avoid unintended text replacements in the captions.
|
| 354 |
+
- Consider caching the captioned video files for frequently requested videos to improve performance and reduce processing time.
|
docs/video/concatenate.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Video Concatenation Endpoint
|
| 2 |
+
|
| 3 |
+
## 1. Overview
|
| 4 |
+
|
| 5 |
+
The `/v1/video/concatenate` endpoint is a part of the Video API and is responsible for combining multiple video files into a single video file. This endpoint fits into the overall API structure as a part of the version 1 (v1) routes, specifically under the `/v1/video` namespace.
|
| 6 |
+
|
| 7 |
+
## 2. Endpoint
|
| 8 |
+
|
| 9 |
+
**URL Path:** `/v1/video/concatenate`
|
| 10 |
+
**HTTP Method:** `POST`
|
| 11 |
+
|
| 12 |
+
## 3. Request
|
| 13 |
+
|
| 14 |
+
### Headers
|
| 15 |
+
|
| 16 |
+
- `x-api-key` (required): The API key for authentication.
|
| 17 |
+
|
| 18 |
+
### Body Parameters
|
| 19 |
+
|
| 20 |
+
The request body must be a JSON object with the following properties:
|
| 21 |
+
|
| 22 |
+
- `video_urls` (required, array of objects): An array of video URLs to be concatenated. Each object in the array must have a `video_url` property (string, URI format) containing the URL of the video file.
|
| 23 |
+
- `webhook_url` (optional, string, URI format): The URL to which the response should be sent as a webhook.
|
| 24 |
+
- `id` (optional, string): An identifier for the request.
|
| 25 |
+
|
| 26 |
+
The `validate_payload` decorator in the routes file enforces the following JSON schema for the request body:
|
| 27 |
+
|
| 28 |
+
```json
|
| 29 |
+
{
|
| 30 |
+
"type": "object",
|
| 31 |
+
"properties": {
|
| 32 |
+
"video_urls": {
|
| 33 |
+
"type": "array",
|
| 34 |
+
"items": {
|
| 35 |
+
"type": "object",
|
| 36 |
+
"properties": {
|
| 37 |
+
"video_url": {"type": "string", "format": "uri"}
|
| 38 |
+
},
|
| 39 |
+
"required": ["video_url"]
|
| 40 |
+
},
|
| 41 |
+
"minItems": 1
|
| 42 |
+
},
|
| 43 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 44 |
+
"id": {"type": "string"}
|
| 45 |
+
},
|
| 46 |
+
"required": ["video_urls"],
|
| 47 |
+
"additionalProperties": False
|
| 48 |
+
}
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
### Example Request
|
| 52 |
+
|
| 53 |
+
```json
|
| 54 |
+
{
|
| 55 |
+
"video_urls": [
|
| 56 |
+
{"video_url": "https://example.com/video1.mp4"},
|
| 57 |
+
{"video_url": "https://example.com/video2.mp4"},
|
| 58 |
+
{"video_url": "https://example.com/video3.mp4"}
|
| 59 |
+
],
|
| 60 |
+
"webhook_url": "https://example.com/webhook",
|
| 61 |
+
"id": "request-123"
|
| 62 |
+
}
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
```bash
|
| 66 |
+
curl -X POST \
|
| 67 |
+
-H "x-api-key: YOUR_API_KEY" \
|
| 68 |
+
-H "Content-Type: application/json" \
|
| 69 |
+
-d '{
|
| 70 |
+
"video_urls": [
|
| 71 |
+
{"video_url": "https://example.com/video1.mp4"},
|
| 72 |
+
{"video_url": "https://example.com/video2.mp4"},
|
| 73 |
+
{"video_url": "https://example.com/video3.mp4"}
|
| 74 |
+
],
|
| 75 |
+
"webhook_url": "https://example.com/webhook",
|
| 76 |
+
"id": "request-123"
|
| 77 |
+
}' \
|
| 78 |
+
https://your-api-endpoint.com/v1/video/concatenate
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
## 4. Response
|
| 82 |
+
|
| 83 |
+
### Success Response
|
| 84 |
+
|
| 85 |
+
The success response follows the general response format defined in the `app.py` file. Here's an example:
|
| 86 |
+
|
| 87 |
+
```json
|
| 88 |
+
{
|
| 89 |
+
"endpoint": "/v1/video/concatenate",
|
| 90 |
+
"code": 200,
|
| 91 |
+
"id": "request-123",
|
| 92 |
+
"job_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
|
| 93 |
+
"response": "https://cloud-storage.example.com/combined-video.mp4",
|
| 94 |
+
"message": "success",
|
| 95 |
+
"pid": 12345,
|
| 96 |
+
"queue_id": 6789,
|
| 97 |
+
"run_time": 10.234,
|
| 98 |
+
"queue_time": 2.345,
|
| 99 |
+
"total_time": 12.579,
|
| 100 |
+
"queue_length": 0,
|
| 101 |
+
"build_number": "1.0.0"
|
| 102 |
+
}
|
| 103 |
+
```
|
| 104 |
+
|
| 105 |
+
The `response` field contains the URL of the combined video file uploaded to cloud storage.
|
| 106 |
+
|
| 107 |
+
### Error Responses
|
| 108 |
+
|
| 109 |
+
- **400 Bad Request**: Returned when the request body is missing or invalid.
|
| 110 |
+
|
| 111 |
+
```json
|
| 112 |
+
{
|
| 113 |
+
"code": 400,
|
| 114 |
+
"message": "Invalid request payload"
|
| 115 |
+
}
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
- **401 Unauthorized**: Returned when the `x-api-key` header is missing or invalid.
|
| 119 |
+
|
| 120 |
+
```json
|
| 121 |
+
{
|
| 122 |
+
"code": 401,
|
| 123 |
+
"message": "Unauthorized"
|
| 124 |
+
}
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
- **429 Too Many Requests**: Returned when the maximum queue length is reached.
|
| 128 |
+
|
| 129 |
+
```json
|
| 130 |
+
{
|
| 131 |
+
"code": 429,
|
| 132 |
+
"id": "request-123",
|
| 133 |
+
"job_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
|
| 134 |
+
"message": "MAX_QUEUE_LENGTH (100) reached",
|
| 135 |
+
"pid": 12345,
|
| 136 |
+
"queue_id": 6789,
|
| 137 |
+
"queue_length": 100,
|
| 138 |
+
"build_number": "1.0.0"
|
| 139 |
+
}
|
| 140 |
+
```
|
| 141 |
+
|
| 142 |
+
- **500 Internal Server Error**: Returned when an unexpected error occurs during the video concatenation process.
|
| 143 |
+
|
| 144 |
+
```json
|
| 145 |
+
{
|
| 146 |
+
"code": 500,
|
| 147 |
+
"message": "An error occurred during video concatenation"
|
| 148 |
+
}
|
| 149 |
+
```
|
| 150 |
+
|
| 151 |
+
## 5. Error Handling
|
| 152 |
+
|
| 153 |
+
The endpoint handles the following common errors:
|
| 154 |
+
|
| 155 |
+
- **Missing or invalid request body**: If the request body is missing or does not conform to the expected JSON schema, a 400 Bad Request error is returned.
|
| 156 |
+
- **Missing or invalid API key**: If the `x-api-key` header is missing or invalid, a 401 Unauthorized error is returned.
|
| 157 |
+
- **Queue length exceeded**: If the maximum queue length is reached (determined by the `MAX_QUEUE_LENGTH` environment variable), a 429 Too Many Requests error is returned.
|
| 158 |
+
- **Unexpected errors during video concatenation**: If an unexpected error occurs during the video concatenation process, a 500 Internal Server Error is returned with the error message.
|
| 159 |
+
|
| 160 |
+
The main application context (`app.py`) also includes error handling for the task queue. If the queue length exceeds the `MAX_QUEUE_LENGTH` limit, the request is rejected with a 429 Too Many Requests error.
|
| 161 |
+
|
| 162 |
+
## 6. Usage Notes
|
| 163 |
+
|
| 164 |
+
- The video files to be concatenated must be accessible via the provided URLs.
|
| 165 |
+
- The order of the video files in the `video_urls` array determines the order in which they will be concatenated.
|
| 166 |
+
- If the `webhook_url` parameter is provided, the response will be sent as a webhook to the specified URL.
|
| 167 |
+
- The `id` parameter can be used to identify the request in the response.
|
| 168 |
+
|
| 169 |
+
## 7. Common Issues
|
| 170 |
+
|
| 171 |
+
- Providing invalid or inaccessible video URLs.
|
| 172 |
+
- Exceeding the maximum queue length, which can lead to requests being rejected with a 429 Too Many Requests error.
|
| 173 |
+
- Encountering unexpected errors during the video concatenation process, which can result in a 500 Internal Server Error.
|
| 174 |
+
|
| 175 |
+
## 8. Best Practices
|
| 176 |
+
|
| 177 |
+
- Validate the video URLs before sending the request to ensure they are accessible and in the correct format.
|
| 178 |
+
- Monitor the queue length and adjust the `MAX_QUEUE_LENGTH` value accordingly to prevent requests from being rejected due to a full queue.
|
| 179 |
+
- Implement retry mechanisms for handling temporary errors or failures during the video concatenation process.
|
| 180 |
+
- Provide meaningful and descriptive `id` values to easily identify requests in the response.
|
docs/video/cut.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Video Cut Endpoint
|
| 2 |
+
|
| 3 |
+
## 1. Overview
|
| 4 |
+
|
| 5 |
+
The `/v1/video/cut` endpoint is part of the Video API and allows users to cut specified segments from a video file with optional encoding settings. This endpoint fits into the overall API structure as a part of the version 1 (`v1`) routes, specifically under the `video` category.
|
| 6 |
+
|
| 7 |
+
## 2. Endpoint
|
| 8 |
+
|
| 9 |
+
```
|
| 10 |
+
POST /v1/video/cut
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
## 3. Request
|
| 14 |
+
|
| 15 |
+
### Headers
|
| 16 |
+
|
| 17 |
+
- `x-api-key` (required): The API key for authentication.
|
| 18 |
+
|
| 19 |
+
### Body Parameters
|
| 20 |
+
|
| 21 |
+
The request body must be a JSON object with the following properties:
|
| 22 |
+
|
| 23 |
+
- `video_url` (required, string): The URL of the video file to be cut.
|
| 24 |
+
- `cuts` (required, array of objects): An array of cut segments, where each object has the following properties:
|
| 25 |
+
- `start` (required, string): The start time of the cut segment in the format `hh:mm:ss.ms`.
|
| 26 |
+
- `end` (required, string): The end time of the cut segment in the format `hh:mm:ss.ms`.
|
| 27 |
+
- `video_codec` (optional, string): The video codec to use for encoding the output video. Default is `libx264`.
|
| 28 |
+
- `video_preset` (optional, string): The video preset to use for encoding the output video. Default is `medium`.
|
| 29 |
+
- `video_crf` (optional, number): The Constant Rate Factor (CRF) value for video encoding. Must be between 0 and 51. Default is 23.
|
| 30 |
+
- `audio_codec` (optional, string): The audio codec to use for encoding the output video. Default is `aac`.
|
| 31 |
+
- `audio_bitrate` (optional, string): The audio bitrate to use for encoding the output video. Default is `128k`.
|
| 32 |
+
- `webhook_url` (optional, string): The URL to receive a webhook notification when the job is completed.
|
| 33 |
+
- `id` (optional, string): A unique identifier for the request.
|
| 34 |
+
|
| 35 |
+
### Example Request
|
| 36 |
+
|
| 37 |
+
```json
|
| 38 |
+
{
|
| 39 |
+
"video_url": "https://example.com/video.mp4",
|
| 40 |
+
"cuts": [
|
| 41 |
+
{
|
| 42 |
+
"start": "00:00:10.000",
|
| 43 |
+
"end": "00:00:20.000"
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"start": "00:00:30.000",
|
| 47 |
+
"end": "00:00:40.000"
|
| 48 |
+
}
|
| 49 |
+
],
|
| 50 |
+
"video_codec": "libx264",
|
| 51 |
+
"video_preset": "medium",
|
| 52 |
+
"video_crf": 23,
|
| 53 |
+
"audio_codec": "aac",
|
| 54 |
+
"audio_bitrate": "128k",
|
| 55 |
+
"webhook_url": "https://example.com/webhook",
|
| 56 |
+
"id": "unique-request-id"
|
| 57 |
+
}
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
```bash
|
| 61 |
+
curl -X POST \
|
| 62 |
+
https://api.example.com/v1/video/cut \
|
| 63 |
+
-H 'x-api-key: YOUR_API_KEY' \
|
| 64 |
+
-H 'Content-Type: application/json' \
|
| 65 |
+
-d '{
|
| 66 |
+
"video_url": "https://example.com/video.mp4",
|
| 67 |
+
"cuts": [
|
| 68 |
+
{
|
| 69 |
+
"start": "00:00:10.000",
|
| 70 |
+
"end": "00:00:20.000"
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
"start": "00:00:30.000",
|
| 74 |
+
"end": "00:00:40.000"
|
| 75 |
+
}
|
| 76 |
+
],
|
| 77 |
+
"video_codec": "libx264",
|
| 78 |
+
"video_preset": "medium",
|
| 79 |
+
"video_crf": 23,
|
| 80 |
+
"audio_codec": "aac",
|
| 81 |
+
"audio_bitrate": "128k",
|
| 82 |
+
"webhook_url": "https://example.com/webhook",
|
| 83 |
+
"id": "unique-request-id"
|
| 84 |
+
}'
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
## 4. Response
|
| 88 |
+
|
| 89 |
+
### Success Response
|
| 90 |
+
|
| 91 |
+
The response follows the general response format defined in the main application context (`app.py`). Here's an example of a successful response:
|
| 92 |
+
|
| 93 |
+
```json
|
| 94 |
+
{
|
| 95 |
+
"endpoint": "/v1/video/cut",
|
| 96 |
+
"code": 200,
|
| 97 |
+
"id": "unique-request-id",
|
| 98 |
+
"job_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
|
| 99 |
+
"response": "https://example.com/processed-video.mp4",
|
| 100 |
+
"message": "success",
|
| 101 |
+
"pid": 12345,
|
| 102 |
+
"queue_id": 6789,
|
| 103 |
+
"run_time": 5.234,
|
| 104 |
+
"queue_time": 0.123,
|
| 105 |
+
"total_time": 5.357,
|
| 106 |
+
"queue_length": 0,
|
| 107 |
+
"build_number": "1.0.0"
|
| 108 |
+
}
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
The `response` field contains the URL of the processed video file.
|
| 112 |
+
|
| 113 |
+
### Error Responses
|
| 114 |
+
|
| 115 |
+
- **400 Bad Request**
|
| 116 |
+
|
| 117 |
+
```json
|
| 118 |
+
{
|
| 119 |
+
"code": 400,
|
| 120 |
+
"message": "Invalid request payload"
|
| 121 |
+
}
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
This error occurs when the request payload is missing required fields or contains invalid data.
|
| 125 |
+
|
| 126 |
+
- **401 Unauthorized**
|
| 127 |
+
|
| 128 |
+
```json
|
| 129 |
+
{
|
| 130 |
+
"code": 401,
|
| 131 |
+
"message": "Invalid API key"
|
| 132 |
+
}
|
| 133 |
+
```
|
| 134 |
+
|
| 135 |
+
This error occurs when the provided `x-api-key` header is missing or invalid.
|
| 136 |
+
|
| 137 |
+
- **429 Too Many Requests**
|
| 138 |
+
|
| 139 |
+
```json
|
| 140 |
+
{
|
| 141 |
+
"code": 429,
|
| 142 |
+
"id": "unique-request-id",
|
| 143 |
+
"job_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
|
| 144 |
+
"message": "MAX_QUEUE_LENGTH (100) reached",
|
| 145 |
+
"pid": 12345,
|
| 146 |
+
"queue_id": 6789,
|
| 147 |
+
"queue_length": 100,
|
| 148 |
+
"build_number": "1.0.0"
|
| 149 |
+
}
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
This error occurs when the maximum queue length has been reached, and the request cannot be processed immediately.
|
| 153 |
+
|
| 154 |
+
- **500 Internal Server Error**
|
| 155 |
+
|
| 156 |
+
```json
|
| 157 |
+
{
|
| 158 |
+
"code": 500,
|
| 159 |
+
"message": "An error occurred during video processing"
|
| 160 |
+
}
|
| 161 |
+
```
|
| 162 |
+
|
| 163 |
+
This error occurs when an unexpected error occurs during the video processing or encoding.
|
| 164 |
+
|
| 165 |
+
## 5. Error Handling
|
| 166 |
+
|
| 167 |
+
The endpoint handles the following common errors:
|
| 168 |
+
|
| 169 |
+
- **Missing or invalid request parameters**: If any required parameters are missing or invalid, the endpoint returns a 400 Bad Request error with an appropriate error message.
|
| 170 |
+
- **Invalid API key**: If the provided `x-api-key` header is missing or invalid, the endpoint returns a 401 Unauthorized error.
|
| 171 |
+
- **Queue limit reached**: If the maximum queue length has been reached, the endpoint returns a 429 Too Many Requests error with the current queue length and the maximum queue length.
|
| 172 |
+
- **Unexpected errors during video processing**: If an unexpected error occurs during the video processing or encoding, the endpoint returns a 500 Internal Server Error with a generic error message.
|
| 173 |
+
|
| 174 |
+
The main application context (`app.py`) also includes error handling for the queue system and webhook notifications.
|
| 175 |
+
|
| 176 |
+
## 6. Usage Notes
|
| 177 |
+
|
| 178 |
+
- The `video_url` parameter must be a valid URL that points to a video file accessible by the server.
|
| 179 |
+
- The `cuts` parameter must be an array of objects, where each object represents a cut segment with a start and end time in the format `hh:mm:ss.ms`.
|
| 180 |
+
- The optional encoding parameters (`video_codec`, `video_preset`, `video_crf`, `audio_codec`, `audio_bitrate`) allow you to customize the encoding settings for the output video file.
|
| 181 |
+
- If the `webhook_url` parameter is provided, the server will send a webhook notification to the specified URL when the job is completed.
|
| 182 |
+
- The `id` parameter can be used to associate the request with a unique identifier for tracking purposes.
|
| 183 |
+
|
| 184 |
+
## 7. Common Issues
|
| 185 |
+
|
| 186 |
+
- Providing an invalid or inaccessible `video_url`.
|
| 187 |
+
- Specifying overlapping or invalid cut segments in the `cuts` parameter.
|
| 188 |
+
- Providing invalid encoding settings that are not supported by the server.
|
| 189 |
+
- Reaching the maximum queue length, which can cause requests to be rejected with a 429 Too Many Requests error.
|
| 190 |
+
|
| 191 |
+
## 8. Best Practices
|
| 192 |
+
|
| 193 |
+
- Validate the `video_url` parameter before sending the request to ensure it points to a valid and accessible video file.
|
| 194 |
+
- Ensure that the cut segments in the `cuts` parameter are correctly formatted and do not overlap or exceed the duration of the video.
|
| 195 |
+
- Use the optional encoding parameters judiciously, as they can impact the processing time and output video quality.
|
| 196 |
+
- Implement retry mechanisms for handling 429 Too Many Requests errors, as the queue length may fluctuate over time.
|
| 197 |
+
- Monitor the webhook notifications or poll the server for job status updates to track the progress of long-running jobs.
|
docs/video/split.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Video Split Endpoint
|
| 2 |
+
|
| 3 |
+
## 1. Overview
|
| 4 |
+
|
| 5 |
+
The `/v1/video/split` endpoint is part of the Video API and is used to split a video file into multiple segments based on specified start and end times. This endpoint fits into the overall API structure as a part of the version 1 (`v1`) routes, specifically under the `video` category.
|
| 6 |
+
|
| 7 |
+
## 2. Endpoint
|
| 8 |
+
|
| 9 |
+
**URL Path:** `/v1/video/split`
|
| 10 |
+
**HTTP Method:** `POST`
|
| 11 |
+
|
| 12 |
+
## 3. Request
|
| 13 |
+
|
| 14 |
+
### Headers
|
| 15 |
+
|
| 16 |
+
- `x-api-key` (required): The API key for authentication.
|
| 17 |
+
|
| 18 |
+
### Body Parameters
|
| 19 |
+
|
| 20 |
+
The request body must be a JSON object with the following properties:
|
| 21 |
+
|
| 22 |
+
- `video_url` (required, string): The URL of the video file to be split.
|
| 23 |
+
- `splits` (required, array of objects): An array of objects specifying the start and end times for each split. Each object must have the following properties:
|
| 24 |
+
- `start` (required, string): The start time of the split in the format `hh:mm:ss.ms`.
|
| 25 |
+
- `end` (required, string): The end time of the split in the format `hh:mm:ss.ms`.
|
| 26 |
+
- `video_codec` (optional, string): The video codec to use for encoding the split videos. Default is `libx264`.
|
| 27 |
+
- `video_preset` (optional, string): The video preset to use for encoding the split videos. Default is `medium`.
|
| 28 |
+
- `video_crf` (optional, number): The Constant Rate Factor (CRF) value for video encoding. Must be between 0 and 51. Default is 23.
|
| 29 |
+
- `audio_codec` (optional, string): The audio codec to use for encoding the split videos. Default is `aac`.
|
| 30 |
+
- `audio_bitrate` (optional, string): The audio bitrate to use for encoding the split videos. Default is `128k`.
|
| 31 |
+
- `webhook_url` (optional, string): The URL to receive a webhook notification when the split operation is complete.
|
| 32 |
+
- `id` (optional, string): A unique identifier for the request.
|
| 33 |
+
|
| 34 |
+
### Example Request
|
| 35 |
+
|
| 36 |
+
```json
|
| 37 |
+
{
|
| 38 |
+
"video_url": "https://example.com/video.mp4",
|
| 39 |
+
"splits": [
|
| 40 |
+
{
|
| 41 |
+
"start": "00:00:10.000",
|
| 42 |
+
"end": "00:00:20.000"
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"start": "00:00:30.000",
|
| 46 |
+
"end": "00:00:40.000"
|
| 47 |
+
}
|
| 48 |
+
],
|
| 49 |
+
"video_codec": "libx264",
|
| 50 |
+
"video_preset": "medium",
|
| 51 |
+
"video_crf": 23,
|
| 52 |
+
"audio_codec": "aac",
|
| 53 |
+
"audio_bitrate": "128k",
|
| 54 |
+
"webhook_url": "https://example.com/webhook",
|
| 55 |
+
"id": "unique-request-id"
|
| 56 |
+
}
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
```bash
|
| 60 |
+
curl -X POST \
|
| 61 |
+
https://api.example.com/v1/video/split \
|
| 62 |
+
-H 'x-api-key: YOUR_API_KEY' \
|
| 63 |
+
-H 'Content-Type: application/json' \
|
| 64 |
+
-d '{
|
| 65 |
+
"video_url": "https://example.com/video.mp4",
|
| 66 |
+
"splits": [
|
| 67 |
+
{
|
| 68 |
+
"start": "00:00:10.000",
|
| 69 |
+
"end": "00:00:20.000"
|
| 70 |
+
},
|
| 71 |
+
{
|
| 72 |
+
"start": "00:00:30.000",
|
| 73 |
+
"end": "00:00:40.000"
|
| 74 |
+
}
|
| 75 |
+
],
|
| 76 |
+
"video_codec": "libx264",
|
| 77 |
+
"video_preset": "medium",
|
| 78 |
+
"video_crf": 23,
|
| 79 |
+
"audio_codec": "aac",
|
| 80 |
+
"audio_bitrate": "128k",
|
| 81 |
+
"webhook_url": "https://example.com/webhook",
|
| 82 |
+
"id": "unique-request-id"
|
| 83 |
+
}'
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
## 4. Response
|
| 87 |
+
|
| 88 |
+
### Success Response
|
| 89 |
+
|
| 90 |
+
The success response follows the general response format specified in `app.py`. Here's an example:
|
| 91 |
+
|
| 92 |
+
```json
|
| 93 |
+
{
|
| 94 |
+
"endpoint": "/v1/video/split",
|
| 95 |
+
"code": 200,
|
| 96 |
+
"id": "unique-request-id",
|
| 97 |
+
"job_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
|
| 98 |
+
"response": [
|
| 99 |
+
{
|
| 100 |
+
"file_url": "https://example.com/split-1.mp4"
|
| 101 |
+
},
|
| 102 |
+
{
|
| 103 |
+
"file_url": "https://example.com/split-2.mp4"
|
| 104 |
+
}
|
| 105 |
+
],
|
| 106 |
+
"message": "success",
|
| 107 |
+
"pid": 12345,
|
| 108 |
+
"queue_id": 6789,
|
| 109 |
+
"run_time": 5.234,
|
| 110 |
+
"queue_time": 0.123,
|
| 111 |
+
"total_time": 5.357,
|
| 112 |
+
"queue_length": 0,
|
| 113 |
+
"build_number": "1.0.0"
|
| 114 |
+
}
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
The `response` field contains an array of objects, each representing a split video file. Each object has a `file_url` property containing the URL of the split video file.
|
| 118 |
+
|
| 119 |
+
### Error Responses
|
| 120 |
+
|
| 121 |
+
- **400 Bad Request**: Returned when the request payload is missing or invalid.
|
| 122 |
+
- **401 Unauthorized**: Returned when the `x-api-key` header is missing or invalid.
|
| 123 |
+
- **429 Too Many Requests**: Returned when the maximum queue length has been reached.
|
| 124 |
+
- **500 Internal Server Error**: Returned when an unexpected error occurs during the video split process.
|
| 125 |
+
|
| 126 |
+
Example error response:
|
| 127 |
+
|
| 128 |
+
```json
|
| 129 |
+
{
|
| 130 |
+
"code": 400,
|
| 131 |
+
"id": "unique-request-id",
|
| 132 |
+
"job_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
|
| 133 |
+
"message": "Invalid request payload: 'splits' is a required property",
|
| 134 |
+
"pid": 12345,
|
| 135 |
+
"queue_id": 6789,
|
| 136 |
+
"queue_length": 2,
|
| 137 |
+
"build_number": "1.0.0"
|
| 138 |
+
}
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
## 5. Error Handling
|
| 142 |
+
|
| 143 |
+
The endpoint handles the following common errors:
|
| 144 |
+
|
| 145 |
+
- Missing or invalid request parameters: Returns a 400 Bad Request error with a descriptive error message.
|
| 146 |
+
- Authentication failure: Returns a 401 Unauthorized error if the `x-api-key` header is missing or invalid.
|
| 147 |
+
- Queue length exceeded: Returns a 429 Too Many Requests error if the maximum queue length has been reached.
|
| 148 |
+
- Unexpected exceptions: Returns a 500 Internal Server Error with the exception message.
|
| 149 |
+
|
| 150 |
+
The main application context (`app.py`) also includes error handling for queue length limits and webhook notifications.
|
| 151 |
+
|
| 152 |
+
## 6. Usage Notes
|
| 153 |
+
|
| 154 |
+
- The `video_url` parameter must be a valid URL pointing to a video file.
|
| 155 |
+
- The `splits` array must contain at least one object specifying the start and end times for a split.
|
| 156 |
+
- The start and end times must be in the format `hh:mm:ss.ms` (hours:minutes:seconds.milliseconds).
|
| 157 |
+
- The `video_codec`, `video_preset`, `video_crf`, `audio_codec`, and `audio_bitrate` parameters are optional and can be used to customize the encoding settings for the split videos.
|
| 158 |
+
- If the `webhook_url` parameter is provided, a webhook notification will be sent to the specified URL when the split operation is complete.
|
| 159 |
+
- The `id` parameter is optional and can be used to uniquely identify the request.
|
| 160 |
+
|
| 161 |
+
## 7. Common Issues
|
| 162 |
+
|
| 163 |
+
- Providing an invalid or inaccessible `video_url`.
|
| 164 |
+
- Specifying overlapping or invalid start and end times in the `splits` array.
|
| 165 |
+
- Exceeding the maximum queue length, which can result in a 429 Too Many Requests error.
|
| 166 |
+
|
| 167 |
+
## 8. Best Practices
|
| 168 |
+
|
| 169 |
+
- Validate the `video_url` parameter before sending the request to ensure it points to a valid video file.
|
| 170 |
+
- Ensure that the start and end times in the `splits` array are correctly formatted and do not overlap.
|
| 171 |
+
- Consider using the `webhook_url` parameter to receive notifications about the completion of the split operation, especially for long-running or asynchronous requests.
|
| 172 |
+
- Implement retry mechanisms and error handling in your client application to handle potential errors and failures.
|
| 173 |
+
- Monitor the queue length and adjust the `MAX_QUEUE_LENGTH` environment variable as needed to prevent excessive queuing and potential timeouts.
|
docs/video/thumbnail.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Video Thumbnail Generation API
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
The `/v1/video/thumbnail` endpoint allows users to extract a thumbnail image from a specific timestamp in a video. This endpoint is part of the video processing capabilities of the API, which includes other features like video concatenation and captioning. The endpoint processes the request asynchronously using a queue system, uploads the generated thumbnail to cloud storage, and returns the URL of the uploaded image.
|
| 6 |
+
|
| 7 |
+
## Endpoint
|
| 8 |
+
|
| 9 |
+
- **URL**: `/v1/video/thumbnail`
|
| 10 |
+
- **Method**: `POST`
|
| 11 |
+
|
| 12 |
+
## Request
|
| 13 |
+
|
| 14 |
+
### Headers
|
| 15 |
+
|
| 16 |
+
- `x-api-key`: Required. Your API authentication key.
|
| 17 |
+
|
| 18 |
+
### Body Parameters
|
| 19 |
+
|
| 20 |
+
| Parameter | Type | Required | Description |
|
| 21 |
+
|-----------|------|----------|-------------|
|
| 22 |
+
| `video_url` | string (URI format) | Yes | URL of the video from which to extract the thumbnail |
|
| 23 |
+
| `second` | number (minimum: 0) | No | Timestamp in seconds at which to extract the thumbnail (defaults to 0) |
|
| 24 |
+
| `webhook_url` | string (URI format) | No | URL to receive the processing result asynchronously |
|
| 25 |
+
| `id` | string | No | Custom identifier for tracking the request |
|
| 26 |
+
|
| 27 |
+
### Example Request
|
| 28 |
+
|
| 29 |
+
```json
|
| 30 |
+
{
|
| 31 |
+
"video_url": "https://example.com/video.mp4",
|
| 32 |
+
"second": 30,
|
| 33 |
+
"webhook_url": "https://your-service.com/webhook",
|
| 34 |
+
"id": "custom-request-123"
|
| 35 |
+
}
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
### Example cURL Command
|
| 39 |
+
|
| 40 |
+
```bash
|
| 41 |
+
curl -X POST \
|
| 42 |
+
https://api.example.com/v1/video/thumbnail \
|
| 43 |
+
-H 'Content-Type: application/json' \
|
| 44 |
+
-H 'x-api-key: your-api-key' \
|
| 45 |
+
-d '{
|
| 46 |
+
"video_url": "https://example.com/video.mp4",
|
| 47 |
+
"second": 30,
|
| 48 |
+
"webhook_url": "https://your-service.com/webhook",
|
| 49 |
+
"id": "custom-request-123"
|
| 50 |
+
}'
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
## Response
|
| 54 |
+
|
| 55 |
+
### Immediate Response (Status Code: 202)
|
| 56 |
+
|
| 57 |
+
When a webhook URL is provided, the API immediately returns a 202 Accepted response and processes the request asynchronously:
|
| 58 |
+
|
| 59 |
+
```json
|
| 60 |
+
{
|
| 61 |
+
"code": 202,
|
| 62 |
+
"id": "custom-request-123",
|
| 63 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 64 |
+
"message": "processing",
|
| 65 |
+
"pid": 12345,
|
| 66 |
+
"queue_id": 67890,
|
| 67 |
+
"max_queue_length": "unlimited",
|
| 68 |
+
"queue_length": 1,
|
| 69 |
+
"build_number": "1.0.0"
|
| 70 |
+
}
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
### Success Response (Status Code: 200)
|
| 74 |
+
|
| 75 |
+
When no webhook URL is provided or when the webhook is called after processing:
|
| 76 |
+
|
| 77 |
+
```json
|
| 78 |
+
{
|
| 79 |
+
"code": 200,
|
| 80 |
+
"id": "custom-request-123",
|
| 81 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 82 |
+
"response": "https://storage.example.com/thumbnails/video-thumbnail-123.jpg",
|
| 83 |
+
"message": "success",
|
| 84 |
+
"run_time": 1.234,
|
| 85 |
+
"queue_time": 0.567,
|
| 86 |
+
"total_time": 1.801,
|
| 87 |
+
"pid": 12345,
|
| 88 |
+
"queue_id": 67890,
|
| 89 |
+
"queue_length": 0,
|
| 90 |
+
"build_number": "1.0.0"
|
| 91 |
+
}
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
### Error Responses
|
| 95 |
+
|
| 96 |
+
#### Invalid Request (Status Code: 400)
|
| 97 |
+
|
| 98 |
+
```json
|
| 99 |
+
{
|
| 100 |
+
"code": 400,
|
| 101 |
+
"message": "Invalid request: 'video_url' is a required property",
|
| 102 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000"
|
| 103 |
+
}
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
#### Queue Full (Status Code: 429)
|
| 107 |
+
|
| 108 |
+
```json
|
| 109 |
+
{
|
| 110 |
+
"code": 429,
|
| 111 |
+
"id": "custom-request-123",
|
| 112 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 113 |
+
"message": "MAX_QUEUE_LENGTH (100) reached",
|
| 114 |
+
"pid": 12345,
|
| 115 |
+
"queue_id": 67890,
|
| 116 |
+
"queue_length": 100,
|
| 117 |
+
"build_number": "1.0.0"
|
| 118 |
+
}
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
#### Server Error (Status Code: 500)
|
| 122 |
+
|
| 123 |
+
```json
|
| 124 |
+
{
|
| 125 |
+
"code": 500,
|
| 126 |
+
"id": "custom-request-123",
|
| 127 |
+
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
| 128 |
+
"message": "Failed to download video from provided URL",
|
| 129 |
+
"pid": 12345,
|
| 130 |
+
"queue_id": 67890,
|
| 131 |
+
"queue_length": 0,
|
| 132 |
+
"build_number": "1.0.0"
|
| 133 |
+
}
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
## Error Handling
|
| 137 |
+
|
| 138 |
+
The endpoint handles various error scenarios:
|
| 139 |
+
|
| 140 |
+
- **Missing Required Parameters**: Returns a 400 error if `video_url` is missing.
|
| 141 |
+
- **Invalid Parameter Format**: Returns a 400 error if parameters don't match the expected format (e.g., invalid URLs).
|
| 142 |
+
- **Queue Capacity**: Returns a 429 error if the processing queue is full.
|
| 143 |
+
- **Processing Errors**: Returns a 500 error if there are issues during thumbnail extraction or upload.
|
| 144 |
+
|
| 145 |
+
## Usage Notes
|
| 146 |
+
|
| 147 |
+
1. **Asynchronous Processing**: For long-running operations, provide a `webhook_url` to receive the result asynchronously.
|
| 148 |
+
2. **Timestamp Selection**: Choose an appropriate `second` value to capture a meaningful frame from the video.
|
| 149 |
+
3. **Request Tracking**: Use the `id` parameter to track your requests across your systems.
|
| 150 |
+
4. **Queue Management**: The API uses a queue system with configurable maximum length (set by the `MAX_QUEUE_LENGTH` environment variable).
|
| 151 |
+
|
| 152 |
+
## Common Issues
|
| 153 |
+
|
| 154 |
+
1. **Inaccessible Video URLs**: Ensure the video URL is publicly accessible or has proper authentication.
|
| 155 |
+
2. **Invalid Timestamp**: If the specified second exceeds the video duration, the API may use the last frame or return an error.
|
| 156 |
+
3. **Webhook Failures**: If your webhook endpoint is unavailable, you won't receive the processing result.
|
| 157 |
+
4. **Large Videos**: Processing very large videos may take longer and could time out.
|
| 158 |
+
|
| 159 |
+
## Best Practices
|
| 160 |
+
|
| 161 |
+
1. **Use Webhooks for Long Videos**: Always use webhooks when processing large videos to avoid HTTP timeout issues.
|
| 162 |
+
2. **Optimize Thumbnail Selection**: Choose meaningful timestamps for thumbnails (e.g., after intro sequences).
|
| 163 |
+
3. **Error Handling**: Implement proper error handling in your application to manage API errors gracefully.
|
| 164 |
+
4. **Rate Limiting**: Monitor the queue length in responses to avoid overwhelming the service.
|
| 165 |
+
5. **Idempotent Requests**: Use the `id` parameter to make requests idempotent and avoid duplicate processing.
|
docs/video/trim.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Video Trim Endpoint
|
| 2 |
+
|
| 3 |
+
## 1. Overview
|
| 4 |
+
|
| 5 |
+
The `/v1/video/trim` endpoint is part of the Video API and allows users to trim a video by removing specified portions from the beginning and/or end. It also provides optional encoding settings to control the output video quality. This endpoint fits into the overall API structure as a part of the version 1 (`v1`) routes, specifically under the `video` category.
|
| 6 |
+
|
| 7 |
+
## 2. Endpoint
|
| 8 |
+
|
| 9 |
+
**URL Path:** `/v1/video/trim`
|
| 10 |
+
**HTTP Method:** `POST`
|
| 11 |
+
|
| 12 |
+
## 3. Request
|
| 13 |
+
|
| 14 |
+
### Headers
|
| 15 |
+
|
| 16 |
+
- `x-api-key` (required): The API key for authentication.
|
| 17 |
+
|
| 18 |
+
### Body Parameters
|
| 19 |
+
|
| 20 |
+
- `video_url` (required, string): The URL of the video file to be trimmed.
|
| 21 |
+
- `start` (optional, string): The start time for trimming in the format `hh:mm:ss` or `mm:ss`.
|
| 22 |
+
- `end` (optional, string): The end time for trimming in the format `hh:mm:ss` or `mm:ss`.
|
| 23 |
+
- `video_codec` (optional, string): The video codec to be used for encoding the output video. Default is `libx264`.
|
| 24 |
+
- `video_preset` (optional, string): The video preset to be used for encoding the output video. Default is `medium`.
|
| 25 |
+
- `video_crf` (optional, number): The Constant Rate Factor (CRF) value for video encoding, ranging from 0 to 51. Default is 23.
|
| 26 |
+
- `audio_codec` (optional, string): The audio codec to be used for encoding the output video. Default is `aac`.
|
| 27 |
+
- `audio_bitrate` (optional, string): The audio bitrate to be used for encoding the output video. Default is `128k`.
|
| 28 |
+
- `webhook_url` (optional, string): The URL to receive a webhook notification upon completion of the task.
|
| 29 |
+
- `id` (optional, string): A unique identifier for the request.
|
| 30 |
+
|
| 31 |
+
The `validate_payload` directive in the routes file ensures that the request payload adheres to the specified schema, which includes the required and optional parameters, their data types, and any additional constraints.
|
| 32 |
+
|
| 33 |
+
### Example Request
|
| 34 |
+
|
| 35 |
+
```json
|
| 36 |
+
{
|
| 37 |
+
"video_url": "https://example.com/video.mp4",
|
| 38 |
+
"start": "00:01:00",
|
| 39 |
+
"end": "00:03:00",
|
| 40 |
+
"video_codec": "libx264",
|
| 41 |
+
"video_preset": "faster",
|
| 42 |
+
"video_crf": 28,
|
| 43 |
+
"audio_codec": "aac",
|
| 44 |
+
"audio_bitrate": "128k",
|
| 45 |
+
"webhook_url": "https://example.com/webhook",
|
| 46 |
+
"id": "unique-request-id"
|
| 47 |
+
}
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
```bash
|
| 51 |
+
curl -X POST \
|
| 52 |
+
https://api.example.com/v1/video/trim \
|
| 53 |
+
-H 'x-api-key: YOUR_API_KEY' \
|
| 54 |
+
-H 'Content-Type: application/json' \
|
| 55 |
+
-d '{
|
| 56 |
+
"video_url": "https://example.com/video.mp4",
|
| 57 |
+
"start": "00:01:00",
|
| 58 |
+
"end": "00:03:00",
|
| 59 |
+
"video_codec": "libx264",
|
| 60 |
+
"video_preset": "faster",
|
| 61 |
+
"video_crf": 28,
|
| 62 |
+
"audio_codec": "aac",
|
| 63 |
+
"audio_bitrate": "128k",
|
| 64 |
+
"webhook_url": "https://example.com/webhook",
|
| 65 |
+
"id": "unique-request-id"
|
| 66 |
+
}'
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
## 4. Response
|
| 70 |
+
|
| 71 |
+
### Success Response
|
| 72 |
+
|
| 73 |
+
The success response follows the general response structure defined in the `app.py` file. Here's an example:
|
| 74 |
+
|
| 75 |
+
```json
|
| 76 |
+
{
|
| 77 |
+
"endpoint": "/v1/video/trim",
|
| 78 |
+
"code": 200,
|
| 79 |
+
"id": "unique-request-id",
|
| 80 |
+
"job_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
|
| 81 |
+
"response": "https://example.com/trimmed-video.mp4",
|
| 82 |
+
"message": "success",
|
| 83 |
+
"pid": 12345,
|
| 84 |
+
"queue_id": 6789,
|
| 85 |
+
"run_time": 5.234,
|
| 86 |
+
"queue_time": 0.123,
|
| 87 |
+
"total_time": 5.357,
|
| 88 |
+
"queue_length": 0,
|
| 89 |
+
"build_number": "1.0.0"
|
| 90 |
+
}
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
### Error Responses
|
| 94 |
+
|
| 95 |
+
- **400 Bad Request**: Returned when the request payload is missing or contains invalid parameters.
|
| 96 |
+
|
| 97 |
+
```json
|
| 98 |
+
{
|
| 99 |
+
"code": 400,
|
| 100 |
+
"message": "Invalid request payload"
|
| 101 |
+
}
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
- **401 Unauthorized**: Returned when the `x-api-key` header is missing or invalid.
|
| 105 |
+
|
| 106 |
+
```json
|
| 107 |
+
{
|
| 108 |
+
"code": 401,
|
| 109 |
+
"message": "Unauthorized"
|
| 110 |
+
}
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
- **500 Internal Server Error**: Returned when an unexpected error occurs during the video trimming process.
|
| 114 |
+
|
| 115 |
+
```json
|
| 116 |
+
{
|
| 117 |
+
"code": 500,
|
| 118 |
+
"message": "An error occurred during the video trimming process"
|
| 119 |
+
}
|
| 120 |
+
```
|
| 121 |
+
|
| 122 |
+
## 5. Error Handling
|
| 123 |
+
|
| 124 |
+
The endpoint handles common errors such as missing or invalid parameters by returning appropriate HTTP status codes and error messages. The `validate_payload` decorator ensures that the request payload adheres to the specified schema, and any violations will result in a 400 Bad Request error.
|
| 125 |
+
|
| 126 |
+
The main application context (`app.py`) includes error handling for the task queue. If the maximum queue length is reached, the endpoint will return a 429 Too Many Requests error with a corresponding message.
|
| 127 |
+
|
| 128 |
+
## 6. Usage Notes
|
| 129 |
+
|
| 130 |
+
- The `start` and `end` parameters are optional, but at least one of them must be provided to perform the trimming operation.
|
| 131 |
+
- The `video_codec`, `video_preset`, `video_crf`, `audio_codec`, and `audio_bitrate` parameters are optional and allow users to customize the encoding settings for the output video.
|
| 132 |
+
- The `webhook_url` parameter is optional and can be used to receive a notification when the task is completed.
|
| 133 |
+
- The `id` parameter is optional and can be used to uniquely identify the request.
|
| 134 |
+
|
| 135 |
+
## 7. Common Issues
|
| 136 |
+
|
| 137 |
+
- Providing an invalid or inaccessible `video_url`.
|
| 138 |
+
- Specifying invalid or unsupported values for the encoding parameters (`video_codec`, `video_preset`, `video_crf`, `audio_codec`, `audio_bitrate`).
|
| 139 |
+
- Encountering issues with the video trimming process due to unsupported video formats or corrupted files.
|
| 140 |
+
|
| 141 |
+
## 8. Best Practices
|
| 142 |
+
|
| 143 |
+
- Validate the `video_url` parameter to ensure it points to a valid and accessible video file.
|
| 144 |
+
- Use appropriate encoding settings based on the desired output quality and file size requirements.
|
| 145 |
+
- Implement error handling and retry mechanisms for failed requests or network issues.
|
| 146 |
+
- Monitor the task queue length and adjust the `MAX_QUEUE_LENGTH` value accordingly to prevent overloading the system.
|
| 147 |
+
- Implement rate limiting or throttling mechanisms to prevent abuse or excessive requests.
|
routes/audio_mixing.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Stephen G. Pope
|
| 2 |
+
#
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by
|
| 5 |
+
# the Free Software Foundation; either version 2 of the License, or
|
| 6 |
+
# (at your option) any later version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful,
|
| 9 |
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 10 |
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 11 |
+
# GNU General Public License for more details.
|
| 12 |
+
#
|
| 13 |
+
# You should have received a copy of the GNU General Public License along
|
| 14 |
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
| 15 |
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
from flask import Blueprint
|
| 20 |
+
from app_utils import *
|
| 21 |
+
import logging
|
| 22 |
+
from services.audio_mixing import process_audio_mixing
|
| 23 |
+
from services.authentication import authenticate
|
| 24 |
+
from services.cloud_storage import upload_file
|
| 25 |
+
|
| 26 |
+
audio_mixing_bp = Blueprint('audio_mixing', __name__)
|
| 27 |
+
logger = logging.getLogger(__name__)
|
| 28 |
+
|
| 29 |
+
@audio_mixing_bp.route('/audio-mixing', methods=['POST'])
|
| 30 |
+
@authenticate
|
| 31 |
+
@validate_payload({
|
| 32 |
+
"type": "object",
|
| 33 |
+
"properties": {
|
| 34 |
+
"video_url": {"type": "string", "format": "uri"},
|
| 35 |
+
"audio_url": {"type": "string", "format": "uri"},
|
| 36 |
+
"video_vol": {"type": "number", "minimum": 0, "maximum": 100},
|
| 37 |
+
"audio_vol": {"type": "number", "minimum": 0, "maximum": 100},
|
| 38 |
+
"output_length": {"type": "string", "enum": ["video", "audio"]},
|
| 39 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 40 |
+
"id": {"type": "string"}
|
| 41 |
+
},
|
| 42 |
+
"required": ["video_url", "audio_url"],
|
| 43 |
+
"additionalProperties": False
|
| 44 |
+
})
|
| 45 |
+
@queue_task_wrapper(bypass_queue=False)
|
| 46 |
+
def audio_mixing(job_id, data):
|
| 47 |
+
video_url = data.get('video_url')
|
| 48 |
+
audio_url = data.get('audio_url')
|
| 49 |
+
video_vol = data.get('video_vol', 100)
|
| 50 |
+
audio_vol = data.get('audio_vol', 100)
|
| 51 |
+
output_length = data.get('output_length', 'video')
|
| 52 |
+
webhook_url = data.get('webhook_url')
|
| 53 |
+
id = data.get('id')
|
| 54 |
+
|
| 55 |
+
logger.info(f"Job {job_id}: Received audio mixing request for {video_url} and {audio_url}")
|
| 56 |
+
|
| 57 |
+
try:
|
| 58 |
+
# Process audio and video mixing
|
| 59 |
+
output_filename = process_audio_mixing(
|
| 60 |
+
video_url, audio_url, video_vol, audio_vol, output_length, job_id, webhook_url
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
# Upload the mixed file using the unified upload_file() method
|
| 64 |
+
cloud_url = upload_file(output_filename)
|
| 65 |
+
|
| 66 |
+
logger.info(f"Job {job_id}: Mixed media uploaded to cloud storage: {cloud_url}")
|
| 67 |
+
|
| 68 |
+
# Return the cloud URL for the uploaded file
|
| 69 |
+
return cloud_url, "/audio-mixing", 200
|
| 70 |
+
|
| 71 |
+
except Exception as e:
|
| 72 |
+
logger.error(f"Job {job_id}: Error during audio mixing process - {str(e)}")
|
| 73 |
+
return str(e), "/audio-mixing", 500
|
routes/authenticate.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Stephen G. Pope
|
| 2 |
+
#
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by
|
| 5 |
+
# the Free Software Foundation; either version 2 of the License, or
|
| 6 |
+
# (at your option) any later version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful,
|
| 9 |
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 10 |
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 11 |
+
# GNU General Public License for more details.
|
| 12 |
+
#
|
| 13 |
+
# You should have received a copy of the GNU General Public License along
|
| 14 |
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
| 15 |
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
from flask import Blueprint, request, jsonify, current_app
|
| 20 |
+
from app_utils import *
|
| 21 |
+
from functools import wraps
|
| 22 |
+
import os
|
| 23 |
+
|
| 24 |
+
auth_bp = Blueprint('auth', __name__)
|
| 25 |
+
|
| 26 |
+
API_KEY = os.environ.get('API_KEY')
|
| 27 |
+
|
| 28 |
+
@auth_bp.route('/authenticate', methods=['GET'])
|
| 29 |
+
@queue_task_wrapper(bypass_queue=True)
|
| 30 |
+
def authenticate_endpoint(**kwargs):
|
| 31 |
+
api_key = request.headers.get('X-API-Key')
|
| 32 |
+
if api_key == API_KEY:
|
| 33 |
+
return "Authorized", "/authenticate", 200
|
| 34 |
+
else:
|
| 35 |
+
return "Unauthorized", "/authenticate", 401
|
routes/caption_video.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Stephen G. Pope
|
| 2 |
+
#
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by
|
| 5 |
+
# the Free Software Foundation; either version 2 of the License, or
|
| 6 |
+
# (at your option) any later version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful,
|
| 9 |
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 10 |
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 11 |
+
# GNU General Public License for more details.
|
| 12 |
+
#
|
| 13 |
+
# You should have received a copy of the GNU General Public License along
|
| 14 |
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
| 15 |
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
from flask import Blueprint, current_app
|
| 20 |
+
from app_utils import *
|
| 21 |
+
import logging
|
| 22 |
+
from services.caption_video import process_captioning
|
| 23 |
+
from services.authentication import authenticate
|
| 24 |
+
from services.cloud_storage import upload_file
|
| 25 |
+
import os
|
| 26 |
+
|
| 27 |
+
caption_bp = Blueprint('caption', __name__)
|
| 28 |
+
logger = logging.getLogger(__name__)
|
| 29 |
+
|
| 30 |
+
@caption_bp.route('/caption-video', methods=['POST'])
|
| 31 |
+
@authenticate
|
| 32 |
+
@validate_payload({
|
| 33 |
+
"type": "object",
|
| 34 |
+
"properties": {
|
| 35 |
+
"video_url": {"type": "string", "format": "uri"},
|
| 36 |
+
"srt": {"type": "string"},
|
| 37 |
+
"ass": {"type": "string"},
|
| 38 |
+
"options": {
|
| 39 |
+
"type": "array",
|
| 40 |
+
"items": {
|
| 41 |
+
"type": "object",
|
| 42 |
+
"properties": {
|
| 43 |
+
"option": {"type": "string"},
|
| 44 |
+
"value": {} # Allow any type for value
|
| 45 |
+
},
|
| 46 |
+
"required": ["option", "value"]
|
| 47 |
+
}
|
| 48 |
+
},
|
| 49 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 50 |
+
"id": {"type": "string"}
|
| 51 |
+
},
|
| 52 |
+
"required": ["video_url"],
|
| 53 |
+
"oneOf": [
|
| 54 |
+
{"required": ["srt"]},
|
| 55 |
+
{"required": ["ass"]}
|
| 56 |
+
],
|
| 57 |
+
"additionalProperties": False
|
| 58 |
+
})
|
| 59 |
+
@queue_task_wrapper(bypass_queue=False)
|
| 60 |
+
def caption_video(job_id, data):
|
| 61 |
+
video_url = data['video_url']
|
| 62 |
+
caption_srt = data.get('srt')
|
| 63 |
+
caption_ass = data.get('ass')
|
| 64 |
+
options = data.get('options', [])
|
| 65 |
+
webhook_url = data.get('webhook_url')
|
| 66 |
+
id = data.get('id')
|
| 67 |
+
|
| 68 |
+
logger.info(f"Job {job_id}: Received captioning request for {video_url}")
|
| 69 |
+
logger.info(f"Job {job_id}: Options received: {options}")
|
| 70 |
+
|
| 71 |
+
if caption_ass is not None:
|
| 72 |
+
captions = caption_ass
|
| 73 |
+
caption_type = "ass"
|
| 74 |
+
else:
|
| 75 |
+
captions = caption_srt
|
| 76 |
+
caption_type = "srt"
|
| 77 |
+
|
| 78 |
+
try:
|
| 79 |
+
output_filename = process_captioning(video_url, captions, caption_type, options, job_id)
|
| 80 |
+
logger.info(f"Job {job_id}: Captioning process completed successfully")
|
| 81 |
+
|
| 82 |
+
# Upload the captioned video using the unified upload_file() method
|
| 83 |
+
cloud_url = upload_file(output_filename)
|
| 84 |
+
|
| 85 |
+
logger.info(f"Job {job_id}: Captioned video uploaded to cloud storage: {cloud_url}")
|
| 86 |
+
|
| 87 |
+
# Return the cloud URL for the uploaded file
|
| 88 |
+
return cloud_url, "/caption-video", 200
|
| 89 |
+
|
| 90 |
+
except Exception as e:
|
| 91 |
+
logger.error(f"Job {job_id}: Error during captioning process - {str(e)}", exc_info=True)
|
| 92 |
+
return str(e), "/caption-video", 500
|
routes/combine_videos.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Stephen G. Pope
|
| 2 |
+
#
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by
|
| 5 |
+
# the Free Software Foundation; either version 2 of the License, or
|
| 6 |
+
# (at your option) any later version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful,
|
| 9 |
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 10 |
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 11 |
+
# GNU General Public License for more details.
|
| 12 |
+
#
|
| 13 |
+
# You should have received a copy of the GNU General Public License along
|
| 14 |
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
| 15 |
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
from flask import Blueprint
|
| 20 |
+
from app_utils import *
|
| 21 |
+
import logging
|
| 22 |
+
from services.ffmpeg_toolkit import process_video_combination
|
| 23 |
+
from services.authentication import authenticate
|
| 24 |
+
from services.cloud_storage import upload_file
|
| 25 |
+
|
| 26 |
+
combine_bp = Blueprint('combine', __name__)
|
| 27 |
+
logger = logging.getLogger(__name__)
|
| 28 |
+
|
| 29 |
+
@combine_bp.route('/combine-videos', methods=['POST'])
|
| 30 |
+
@authenticate
|
| 31 |
+
@validate_payload({
|
| 32 |
+
"type": "object",
|
| 33 |
+
"properties": {
|
| 34 |
+
"video_urls": {
|
| 35 |
+
"type": "array",
|
| 36 |
+
"items": {
|
| 37 |
+
"type": "object",
|
| 38 |
+
"properties": {
|
| 39 |
+
"video_url": {"type": "string", "format": "uri"}
|
| 40 |
+
},
|
| 41 |
+
"required": ["video_url"]
|
| 42 |
+
},
|
| 43 |
+
"minItems": 1
|
| 44 |
+
},
|
| 45 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 46 |
+
"id": {"type": "string"}
|
| 47 |
+
},
|
| 48 |
+
"required": ["video_urls"],
|
| 49 |
+
"additionalProperties": False
|
| 50 |
+
})
|
| 51 |
+
@queue_task_wrapper(bypass_queue=False)
|
| 52 |
+
def combine_videos(job_id, data):
|
| 53 |
+
media_urls = data['video_urls']
|
| 54 |
+
webhook_url = data.get('webhook_url')
|
| 55 |
+
id = data.get('id')
|
| 56 |
+
|
| 57 |
+
logger.info(f"Job {job_id}: Received combine-videos request for {len(media_urls)} videos")
|
| 58 |
+
|
| 59 |
+
try:
|
| 60 |
+
output_file = process_video_combination(media_urls, job_id)
|
| 61 |
+
logger.info(f"Job {job_id}: Video combination process completed successfully")
|
| 62 |
+
|
| 63 |
+
cloud_url = upload_file(output_file)
|
| 64 |
+
logger.info(f"Job {job_id}: Combined video uploaded to cloud storage: {cloud_url}")
|
| 65 |
+
|
| 66 |
+
return cloud_url, "/combine-videos", 200
|
| 67 |
+
|
| 68 |
+
except Exception as e:
|
| 69 |
+
logger.error(f"Job {job_id}: Error during video combination process - {str(e)}")
|
| 70 |
+
return str(e), "/combine-videos", 500
|
routes/extract_keyframes.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Stephen G. Pope
|
| 2 |
+
#
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by
|
| 5 |
+
# the Free Software Foundation; either version 2 of the License, or
|
| 6 |
+
# (at your option) any later version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful,
|
| 9 |
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 10 |
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 11 |
+
# GNU General Public License for more details.
|
| 12 |
+
#
|
| 13 |
+
# You should have received a copy of the GNU General Public License along
|
| 14 |
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
| 15 |
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
from flask import Blueprint
|
| 20 |
+
from app_utils import *
|
| 21 |
+
import logging
|
| 22 |
+
from services.extract_keyframes import process_keyframe_extraction
|
| 23 |
+
from services.authentication import authenticate
|
| 24 |
+
from services.cloud_storage import upload_file
|
| 25 |
+
|
| 26 |
+
extract_keyframes_bp = Blueprint('extract_keyframes', __name__)
|
| 27 |
+
logger = logging.getLogger(__name__)
|
| 28 |
+
|
| 29 |
+
@extract_keyframes_bp.route('/extract-keyframes', methods=['POST'])
|
| 30 |
+
@authenticate
|
| 31 |
+
@validate_payload({
|
| 32 |
+
"type": "object",
|
| 33 |
+
"properties": {
|
| 34 |
+
"video_url": {"type": "string", "format": "uri"},
|
| 35 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 36 |
+
"id": {"type": "string"}
|
| 37 |
+
},
|
| 38 |
+
"required": ["video_url"],
|
| 39 |
+
"additionalProperties": False
|
| 40 |
+
})
|
| 41 |
+
@queue_task_wrapper(bypass_queue=False)
|
| 42 |
+
def extract_keyframes(job_id, data):
|
| 43 |
+
video_url = data.get('video_url')
|
| 44 |
+
webhook_url = data.get('webhook_url')
|
| 45 |
+
id = data.get('id')
|
| 46 |
+
|
| 47 |
+
logger.info(f"Job {job_id}: Received keyframe extraction request for {video_url}")
|
| 48 |
+
|
| 49 |
+
try:
|
| 50 |
+
# Process keyframe extraction
|
| 51 |
+
image_paths = process_keyframe_extraction(video_url, job_id)
|
| 52 |
+
|
| 53 |
+
# Upload each extracted keyframe and collect the cloud URLs
|
| 54 |
+
image_urls = []
|
| 55 |
+
for image_path in image_paths:
|
| 56 |
+
cloud_url = upload_file(image_path)
|
| 57 |
+
image_urls.append({"image_url": cloud_url})
|
| 58 |
+
|
| 59 |
+
logger.info(f"Job {job_id}: Keyframes uploaded to cloud storage")
|
| 60 |
+
|
| 61 |
+
# Return the URLs of the uploaded keyframes
|
| 62 |
+
return {"image_urls": image_urls}, "/extract-keyframes", 200
|
| 63 |
+
|
| 64 |
+
except Exception as e:
|
| 65 |
+
logger.error(f"Job {job_id}: Error during keyframe extraction - {str(e)}")
|
| 66 |
+
return str(e), "/extract-keyframes", 500
|
routes/gdrive_upload.py
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Stephen G. Pope
|
| 2 |
+
#
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by
|
| 5 |
+
# the Free Software Foundation; either version 2 of the License, or
|
| 6 |
+
# (at your option) any later version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful,
|
| 9 |
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 10 |
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 11 |
+
# GNU General Public License for more details.
|
| 12 |
+
#
|
| 13 |
+
# You should have received a copy of the GNU General Public License along
|
| 14 |
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
| 15 |
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
import os
|
| 20 |
+
import logging
|
| 21 |
+
from flask import Blueprint, request, jsonify
|
| 22 |
+
import threading
|
| 23 |
+
import requests
|
| 24 |
+
import uuid
|
| 25 |
+
import json
|
| 26 |
+
from google.oauth2.service_account import Credentials
|
| 27 |
+
from google.auth.transport.requests import Request
|
| 28 |
+
from datetime import datetime
|
| 29 |
+
import time
|
| 30 |
+
import psutil
|
| 31 |
+
from services.authentication import authenticate
|
| 32 |
+
from app_utils import validate_payload, queue_task_wrapper
|
| 33 |
+
|
| 34 |
+
# Configure logging
|
| 35 |
+
logging.basicConfig(level=logging.INFO)
|
| 36 |
+
logger = logging.getLogger(__name__)
|
| 37 |
+
|
| 38 |
+
# Define the blueprint
|
| 39 |
+
gdrive_upload_bp = Blueprint('gdrive_upload', __name__)
|
| 40 |
+
|
| 41 |
+
# Environment variables
|
| 42 |
+
GCP_SA_CREDENTIALS = os.getenv('GCP_SA_CREDENTIALS')
|
| 43 |
+
GDRIVE_USER = os.getenv('GDRIVE_USER')
|
| 44 |
+
|
| 45 |
+
# Class to track upload progress
|
| 46 |
+
class UploadProgress:
|
| 47 |
+
def __init__(self, job_id, total_size):
|
| 48 |
+
self.job_id = job_id
|
| 49 |
+
self.total_size = total_size
|
| 50 |
+
self.bytes_uploaded = 0
|
| 51 |
+
self.start_time = time.time()
|
| 52 |
+
self.lock = threading.Lock()
|
| 53 |
+
self.last_logged_percentage = 0
|
| 54 |
+
self.last_logged_resource_percentage = 0 # For memory/disk logging every 5%
|
| 55 |
+
|
| 56 |
+
# Global list to keep track of active uploads
|
| 57 |
+
active_uploads = []
|
| 58 |
+
uploads_lock = threading.Lock()
|
| 59 |
+
|
| 60 |
+
def get_access_token():
|
| 61 |
+
"""
|
| 62 |
+
Retrieves an access token for Google APIs using service account credentials.
|
| 63 |
+
"""
|
| 64 |
+
credentials_info = json.loads(GCP_SA_CREDENTIALS)
|
| 65 |
+
credentials = Credentials.from_service_account_info(
|
| 66 |
+
credentials_info,
|
| 67 |
+
scopes=['https://www.googleapis.com/auth/drive']
|
| 68 |
+
)
|
| 69 |
+
delegated_credentials = credentials.with_subject(GDRIVE_USER)
|
| 70 |
+
if not delegated_credentials.valid or delegated_credentials.expired:
|
| 71 |
+
delegated_credentials.refresh(Request())
|
| 72 |
+
access_token = delegated_credentials.token
|
| 73 |
+
return access_token
|
| 74 |
+
|
| 75 |
+
def initiate_resumable_upload(filename, folder_id, mime_type='application/octet-stream'):
|
| 76 |
+
"""
|
| 77 |
+
Initiates a resumable upload session with Google Drive and returns the upload URL.
|
| 78 |
+
"""
|
| 79 |
+
url = 'https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable'
|
| 80 |
+
headers = {
|
| 81 |
+
'Authorization': f'Bearer {get_access_token()}',
|
| 82 |
+
'Content-Type': 'application/json; charset=UTF-8',
|
| 83 |
+
'X-Upload-Content-Type': mime_type
|
| 84 |
+
}
|
| 85 |
+
metadata = {
|
| 86 |
+
'name': filename,
|
| 87 |
+
'parents': [folder_id]
|
| 88 |
+
}
|
| 89 |
+
response = requests.post(url, headers=headers, data=json.dumps(metadata))
|
| 90 |
+
response.raise_for_status()
|
| 91 |
+
upload_url = response.headers['Location']
|
| 92 |
+
return upload_url
|
| 93 |
+
|
| 94 |
+
def upload_file_in_chunks(file_url, upload_url, total_size, job_id, chunk_size):
|
| 95 |
+
"""
|
| 96 |
+
Uploads the file to Google Drive in chunks by streaming data directly from the source URL.
|
| 97 |
+
"""
|
| 98 |
+
bytes_uploaded = 0
|
| 99 |
+
max_retries = 5
|
| 100 |
+
retry_delay = 5 # seconds
|
| 101 |
+
|
| 102 |
+
progress = UploadProgress(job_id, total_size)
|
| 103 |
+
|
| 104 |
+
# Add progress to active_uploads
|
| 105 |
+
with uploads_lock:
|
| 106 |
+
active_uploads.append(progress)
|
| 107 |
+
|
| 108 |
+
try:
|
| 109 |
+
with requests.get(file_url, stream=True) as r:
|
| 110 |
+
r.raise_for_status()
|
| 111 |
+
iterator = r.iter_content(chunk_size=chunk_size)
|
| 112 |
+
for chunk in iterator:
|
| 113 |
+
if chunk:
|
| 114 |
+
for attempt in range(max_retries):
|
| 115 |
+
start = bytes_uploaded
|
| 116 |
+
end = bytes_uploaded + len(chunk) - 1
|
| 117 |
+
content_range = f'bytes {start}-{end}/{total_size}'
|
| 118 |
+
headers = {
|
| 119 |
+
'Content-Length': str(len(chunk)),
|
| 120 |
+
'Content-Range': content_range,
|
| 121 |
+
}
|
| 122 |
+
try:
|
| 123 |
+
upload_response = requests.put(
|
| 124 |
+
upload_url,
|
| 125 |
+
headers=headers,
|
| 126 |
+
data=chunk
|
| 127 |
+
)
|
| 128 |
+
if upload_response.status_code in (200, 201):
|
| 129 |
+
# Upload complete
|
| 130 |
+
logger.info(f"Job {job_id}: Upload complete.")
|
| 131 |
+
with progress.lock:
|
| 132 |
+
progress.bytes_uploaded = end + 1
|
| 133 |
+
return upload_response.json()['id']
|
| 134 |
+
elif upload_response.status_code == 308:
|
| 135 |
+
# Resumable upload incomplete
|
| 136 |
+
bytes_uploaded = end + 1
|
| 137 |
+
with progress.lock:
|
| 138 |
+
progress.bytes_uploaded = bytes_uploaded
|
| 139 |
+
break # Break retry loop and continue with next chunk
|
| 140 |
+
else:
|
| 141 |
+
# Handle unexpected status codes
|
| 142 |
+
logger.error(f"Job {job_id}: Unexpected status code: {upload_response.status_code}")
|
| 143 |
+
raise Exception(f"Upload failed with status code {upload_response.status_code}")
|
| 144 |
+
except requests.exceptions.RequestException as e:
|
| 145 |
+
logger.error(f"Job {job_id}: Network error during upload: {e}")
|
| 146 |
+
if attempt < max_retries - 1:
|
| 147 |
+
logger.info(f"Job {job_id}: Retrying upload chunk after {retry_delay} seconds...")
|
| 148 |
+
time.sleep(retry_delay)
|
| 149 |
+
continue
|
| 150 |
+
else:
|
| 151 |
+
logger.error(f"Job {job_id}: Max retries reached. Upload failed.")
|
| 152 |
+
raise
|
| 153 |
+
else:
|
| 154 |
+
# If we exhausted retries, exit the function
|
| 155 |
+
raise Exception("Failed to upload chunk after multiple retries.")
|
| 156 |
+
finally:
|
| 157 |
+
# Remove progress from active_uploads
|
| 158 |
+
with uploads_lock:
|
| 159 |
+
if progress in active_uploads:
|
| 160 |
+
active_uploads.remove(progress)
|
| 161 |
+
|
| 162 |
+
@gdrive_upload_bp.route('/gdrive-upload', methods=['POST'])
|
| 163 |
+
@authenticate
|
| 164 |
+
@validate_payload({
|
| 165 |
+
"type": "object",
|
| 166 |
+
"properties": {
|
| 167 |
+
"file_url": {"type": "string", "format": "uri"},
|
| 168 |
+
"filename": {"type": "string"},
|
| 169 |
+
"folder_id": {"type": "string"},
|
| 170 |
+
"mime_type": {"type": "string"},
|
| 171 |
+
"chunk_size": {"type": "integer", "minimum": 1},
|
| 172 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 173 |
+
"id": {"type": "string"}
|
| 174 |
+
},
|
| 175 |
+
"required": ["file_url", "filename", "folder_id"],
|
| 176 |
+
"additionalProperties": False
|
| 177 |
+
})
|
| 178 |
+
@queue_task_wrapper(bypass_queue=False)
|
| 179 |
+
def gdrive_upload(job_id, data):
|
| 180 |
+
logger.info(f"Processing Job ID: {job_id}")
|
| 181 |
+
|
| 182 |
+
if not GDRIVE_USER:
|
| 183 |
+
logger.error("GDRIVE_USER environment variable is not set")
|
| 184 |
+
return "GDRIVE_USER environment variable is not set", "/gdrive-upload", 400
|
| 185 |
+
|
| 186 |
+
try:
|
| 187 |
+
file_url = data['file_url']
|
| 188 |
+
filename = data['filename']
|
| 189 |
+
folder_id = data['folder_id']
|
| 190 |
+
mime_type = data.get('mime_type', 'application/octet-stream')
|
| 191 |
+
chunk_size = data.get('chunk_size', 5 * 1024 * 1024) # Default to 5 MB
|
| 192 |
+
|
| 193 |
+
# Get the total size of the file
|
| 194 |
+
try:
|
| 195 |
+
head_response = requests.head(file_url, allow_redirects=True, timeout=30)
|
| 196 |
+
head_response.raise_for_status()
|
| 197 |
+
total_size = int(head_response.headers.get('Content-Length', 0))
|
| 198 |
+
|
| 199 |
+
get_response = requests.get(file_url, stream=True, timeout=30)
|
| 200 |
+
get_response.raise_for_status()
|
| 201 |
+
total_size = int(get_response.headers.get('Content-Length', 0))
|
| 202 |
+
if total_size == 0:
|
| 203 |
+
raise ValueError("Content-Length header is missing or zero")
|
| 204 |
+
except requests.exceptions.RequestException as e:
|
| 205 |
+
logger.error(f"Job {job_id}: Error accessing file URL: {str(e)}")
|
| 206 |
+
return f"Error accessing file URL: {str(e)}", "/gdrive-upload", 500
|
| 207 |
+
except ValueError as e:
|
| 208 |
+
logger.error(f"Job {job_id}: {str(e)}")
|
| 209 |
+
return f"Unable to determine file size: {str(e)}", "/gdrive-upload", 500
|
| 210 |
+
|
| 211 |
+
logger.info(f"Job {job_id}: File size determined: {total_size} bytes")
|
| 212 |
+
|
| 213 |
+
# Initiate upload session
|
| 214 |
+
upload_url = initiate_resumable_upload(filename, folder_id, mime_type)
|
| 215 |
+
logger.info(f"Job {job_id}: Resumable upload session initiated with chunk size {chunk_size} bytes.")
|
| 216 |
+
|
| 217 |
+
# Upload file in chunks
|
| 218 |
+
file_id = upload_file_in_chunks(file_url, upload_url, total_size, job_id, chunk_size)
|
| 219 |
+
|
| 220 |
+
return file_id, "/gdrive-upload", 200
|
| 221 |
+
|
| 222 |
+
except Exception as e:
|
| 223 |
+
logger.error(f"Job {job_id}: Error during processing - {str(e)}")
|
| 224 |
+
return str(e), "/gdrive-upload", 500
|
| 225 |
+
|
| 226 |
+
def log_system_resources():
|
| 227 |
+
"""
|
| 228 |
+
Logs system resource usage and upload progress at regular intervals.
|
| 229 |
+
"""
|
| 230 |
+
while True:
|
| 231 |
+
# Get memory and disk usage
|
| 232 |
+
memory_info = psutil.virtual_memory()
|
| 233 |
+
disk_info = psutil.disk_usage('/')
|
| 234 |
+
|
| 235 |
+
with uploads_lock:
|
| 236 |
+
for progress in active_uploads:
|
| 237 |
+
with progress.lock:
|
| 238 |
+
# Calculate the percentage uploaded
|
| 239 |
+
percentage = (progress.bytes_uploaded / progress.total_size) * 100 if progress.total_size > 0 else 0
|
| 240 |
+
elapsed_time = time.time() - progress.start_time
|
| 241 |
+
|
| 242 |
+
# Log upload progress every 1%
|
| 243 |
+
if int(percentage) >= progress.last_logged_percentage + 1:
|
| 244 |
+
progress.last_logged_percentage = int(percentage)
|
| 245 |
+
logger.info(
|
| 246 |
+
f"Job {progress.job_id}: Uploaded {progress.bytes_uploaded} of {progress.total_size} bytes "
|
| 247 |
+
f"({percentage:.2f}%), Elapsed Time: {int(elapsed_time)} seconds"
|
| 248 |
+
)
|
| 249 |
+
|
| 250 |
+
# Log system resource usage every 5%
|
| 251 |
+
if int(percentage) >= progress.last_logged_resource_percentage + 5:
|
| 252 |
+
progress.last_logged_resource_percentage = int(percentage)
|
| 253 |
+
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
| 254 |
+
logger.info(f"[{current_time}] Memory Usage: {memory_info.percent}% used")
|
| 255 |
+
logger.info(f"[{current_time}] Disk Usage: {disk_info.percent}% used")
|
| 256 |
+
|
| 257 |
+
# Sleep for 1 second before the next update
|
| 258 |
+
time.sleep(1)
|
| 259 |
+
|
| 260 |
+
# Start the resource logging in a separate thread
|
| 261 |
+
resource_logging_thread = threading.Thread(
|
| 262 |
+
target=log_system_resources,
|
| 263 |
+
daemon=True
|
| 264 |
+
)
|
| 265 |
+
resource_logging_thread.start()
|
routes/image_to_video.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Stephen G. Pope
|
| 2 |
+
#
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by
|
| 5 |
+
# the Free Software Foundation; either version 2 of the License, or
|
| 6 |
+
# (at your option) any later version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful,
|
| 9 |
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 10 |
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 11 |
+
# GNU General Public License for more details.
|
| 12 |
+
#
|
| 13 |
+
# You should have received a copy of the GNU General Public License along
|
| 14 |
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
| 15 |
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
from flask import Blueprint
|
| 20 |
+
from app_utils import *
|
| 21 |
+
import logging
|
| 22 |
+
from services.image_to_video import process_image_to_video
|
| 23 |
+
from services.authentication import authenticate
|
| 24 |
+
from services.cloud_storage import upload_file
|
| 25 |
+
|
| 26 |
+
image_to_video_bp = Blueprint('image_to_video', __name__)
|
| 27 |
+
logger = logging.getLogger(__name__)
|
| 28 |
+
|
| 29 |
+
@image_to_video_bp.route('/image-to-video', methods=['POST'])
|
| 30 |
+
@authenticate
|
| 31 |
+
@validate_payload({
|
| 32 |
+
"type": "object",
|
| 33 |
+
"properties": {
|
| 34 |
+
"image_url": {"type": "string", "format": "uri"},
|
| 35 |
+
"length": {"type": "number", "minimum": 1, "maximum": 60},
|
| 36 |
+
"frame_rate": {"type": "integer", "minimum": 15, "maximum": 60},
|
| 37 |
+
"zoom_speed": {"type": "number", "minimum": 0, "maximum": 100},
|
| 38 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 39 |
+
"id": {"type": "string"}
|
| 40 |
+
},
|
| 41 |
+
"required": ["image_url"],
|
| 42 |
+
"additionalProperties": False
|
| 43 |
+
})
|
| 44 |
+
@queue_task_wrapper(bypass_queue=False)
|
| 45 |
+
def image_to_video(job_id, data):
|
| 46 |
+
image_url = data.get('image_url')
|
| 47 |
+
length = data.get('length', 5)
|
| 48 |
+
frame_rate = data.get('frame_rate', 30)
|
| 49 |
+
zoom_speed = data.get('zoom_speed', 3) / 100
|
| 50 |
+
webhook_url = data.get('webhook_url')
|
| 51 |
+
id = data.get('id')
|
| 52 |
+
|
| 53 |
+
logger.info(f"Job {job_id}: Received image to video request for {image_url}")
|
| 54 |
+
|
| 55 |
+
try:
|
| 56 |
+
# Process image to video conversion
|
| 57 |
+
output_filename = process_image_to_video(
|
| 58 |
+
image_url, length, frame_rate, zoom_speed, job_id, webhook_url
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
# Upload the resulting file using the unified upload_file() method
|
| 62 |
+
cloud_url = upload_file(output_filename)
|
| 63 |
+
|
| 64 |
+
# Log the successful upload
|
| 65 |
+
logger.info(f"Job {job_id}: Converted video uploaded to cloud storage: {cloud_url}")
|
| 66 |
+
|
| 67 |
+
# Return the cloud URL for the uploaded file
|
| 68 |
+
return cloud_url, "/image-to-video", 200
|
| 69 |
+
|
| 70 |
+
except Exception as e:
|
| 71 |
+
logger.error(f"Job {job_id}: Error processing image to video: {str(e)}", exc_info=True)
|
| 72 |
+
return str(e), "/image-to-video", 500
|
routes/media_to_mp3.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Stephen G. Pope
|
| 2 |
+
#
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by
|
| 5 |
+
# the Free Software Foundation; either version 2 of the License, or
|
| 6 |
+
# (at your option) any later version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful,
|
| 9 |
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 10 |
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 11 |
+
# GNU General Public License for more details.
|
| 12 |
+
#
|
| 13 |
+
# You should have received a copy of the GNU General Public License along
|
| 14 |
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
| 15 |
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
# routes/media_to_mp3.py
|
| 20 |
+
from flask import Blueprint, current_app
|
| 21 |
+
from app_utils import *
|
| 22 |
+
import logging
|
| 23 |
+
from services.ffmpeg_toolkit import process_conversion
|
| 24 |
+
from services.authentication import authenticate
|
| 25 |
+
from services.cloud_storage import upload_file
|
| 26 |
+
import os
|
| 27 |
+
|
| 28 |
+
convert_bp = Blueprint('convert', __name__)
|
| 29 |
+
logger = logging.getLogger(__name__)
|
| 30 |
+
|
| 31 |
+
@convert_bp.route('/media-to-mp3', methods=['POST'])
|
| 32 |
+
@authenticate
|
| 33 |
+
@validate_payload({
|
| 34 |
+
"type": "object",
|
| 35 |
+
"properties": {
|
| 36 |
+
"media_url": {"type": "string", "format": "uri"},
|
| 37 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 38 |
+
"id": {"type": "string"},
|
| 39 |
+
"bitrate": {"type": "string", "pattern": "^[0-9]+k$"}
|
| 40 |
+
},
|
| 41 |
+
"required": ["media_url"],
|
| 42 |
+
"additionalProperties": False
|
| 43 |
+
})
|
| 44 |
+
@queue_task_wrapper(bypass_queue=False)
|
| 45 |
+
def convert_media_to_mp3(job_id, data):
|
| 46 |
+
media_url = data['media_url']
|
| 47 |
+
webhook_url = data.get('webhook_url')
|
| 48 |
+
id = data.get('id')
|
| 49 |
+
bitrate = data.get('bitrate', '128k')
|
| 50 |
+
|
| 51 |
+
logger.info(f"Job {job_id}: Received media-to-mp3 request for media URL: {media_url}")
|
| 52 |
+
|
| 53 |
+
try:
|
| 54 |
+
output_file = process_conversion(media_url, job_id, bitrate)
|
| 55 |
+
logger.info(f"Job {job_id}: Media conversion process completed successfully")
|
| 56 |
+
|
| 57 |
+
cloud_url = upload_file(output_file)
|
| 58 |
+
logger.info(f"Job {job_id}: Converted media uploaded to cloud storage: {cloud_url}")
|
| 59 |
+
|
| 60 |
+
return cloud_url, "/media-to-mp3", 200
|
| 61 |
+
|
| 62 |
+
except Exception as e:
|
| 63 |
+
logger.error(f"Job {job_id}: Error during media conversion process - {str(e)}")
|
| 64 |
+
return str(e), "/media-to-mp3", 500
|
routes/transcribe_media.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Stephen G. Pope
|
| 2 |
+
#
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by
|
| 5 |
+
# the Free Software Foundation; either version 2 of the License, or
|
| 6 |
+
# (at your option) any later version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful,
|
| 9 |
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 10 |
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 11 |
+
# GNU General Public License for more details.
|
| 12 |
+
#
|
| 13 |
+
# You should have received a copy of the GNU General Public License along
|
| 14 |
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
| 15 |
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
from flask import Blueprint
|
| 20 |
+
from app_utils import *
|
| 21 |
+
import logging
|
| 22 |
+
import os
|
| 23 |
+
from services.transcription import process_transcription
|
| 24 |
+
from services.authentication import authenticate
|
| 25 |
+
from services.cloud_storage import upload_file
|
| 26 |
+
|
| 27 |
+
transcribe_bp = Blueprint('transcribe', __name__)
|
| 28 |
+
logger = logging.getLogger(__name__)
|
| 29 |
+
|
| 30 |
+
@transcribe_bp.route('/transcribe-media', methods=['POST'])
|
| 31 |
+
@authenticate
|
| 32 |
+
@validate_payload({
|
| 33 |
+
"type": "object",
|
| 34 |
+
"properties": {
|
| 35 |
+
"media_url": {"type": "string", "format": "uri"},
|
| 36 |
+
"output": {"type": "string", "enum": ["transcript", "srt", "vtt", "ass"]},
|
| 37 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 38 |
+
"max_chars": {"type": "integer"},
|
| 39 |
+
"id": {"type": "string"}
|
| 40 |
+
},
|
| 41 |
+
"required": ["media_url"],
|
| 42 |
+
"additionalProperties": False
|
| 43 |
+
})
|
| 44 |
+
@queue_task_wrapper(bypass_queue=False)
|
| 45 |
+
def transcribe(job_id, data):
|
| 46 |
+
media_url = data['media_url']
|
| 47 |
+
output = data.get('output', 'transcript')
|
| 48 |
+
webhook_url = data.get('webhook_url')
|
| 49 |
+
max_chars = data.get('max_chars', 56)
|
| 50 |
+
id = data.get('id')
|
| 51 |
+
|
| 52 |
+
logger.info(f"Job {job_id}: Received transcription request for {media_url}")
|
| 53 |
+
|
| 54 |
+
try:
|
| 55 |
+
result = process_transcription(media_url, output, max_chars)
|
| 56 |
+
logger.info(f"Job {job_id}: Transcription process completed successfully")
|
| 57 |
+
|
| 58 |
+
# If the result is a file path, upload it using the unified upload_file() method
|
| 59 |
+
if output in ['srt', 'vtt', 'ass']:
|
| 60 |
+
cloud_url = upload_file(result)
|
| 61 |
+
os.remove(result) # Remove the temporary file after uploading
|
| 62 |
+
return cloud_url, "/transcribe-media", 200
|
| 63 |
+
else:
|
| 64 |
+
return result, "/transcribe-media", 200
|
| 65 |
+
|
| 66 |
+
except Exception as e:
|
| 67 |
+
logger.error(f"Job {job_id}: Error during transcription process - {str(e)}")
|
| 68 |
+
return str(e), "/transcribe-media", 500
|
routes/v1/audio/concatenate.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Blueprint
|
| 2 |
+
from app_utils import *
|
| 3 |
+
import logging
|
| 4 |
+
from services.v1.audio.concatenate import process_audio_concatenate
|
| 5 |
+
from services.authentication import authenticate
|
| 6 |
+
from services.cloud_storage import upload_file
|
| 7 |
+
|
| 8 |
+
v1_audio_concatenate_bp = Blueprint("v1_audio_concatenate", __name__)
|
| 9 |
+
logger = logging.getLogger(__name__)
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@v1_audio_concatenate_bp.route("/v1/audio/concatenate", methods=["POST"])
|
| 13 |
+
@authenticate
|
| 14 |
+
@validate_payload(
|
| 15 |
+
{
|
| 16 |
+
"type": "object",
|
| 17 |
+
"properties": {
|
| 18 |
+
"audio_urls": {
|
| 19 |
+
"type": "array",
|
| 20 |
+
"items": {
|
| 21 |
+
"type": "object",
|
| 22 |
+
"properties": {"audio_url": {"type": "string", "format": "uri"}},
|
| 23 |
+
"required": ["audio_url"],
|
| 24 |
+
},
|
| 25 |
+
"minItems": 1,
|
| 26 |
+
},
|
| 27 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 28 |
+
"id": {"type": "string"},
|
| 29 |
+
},
|
| 30 |
+
"required": ["audio_urls"],
|
| 31 |
+
"additionalProperties": False,
|
| 32 |
+
}
|
| 33 |
+
)
|
| 34 |
+
@queue_task_wrapper(bypass_queue=False)
|
| 35 |
+
def combine_audio(job_id, data):
|
| 36 |
+
media_urls = data["audio_urls"]
|
| 37 |
+
webhook_url = data.get("webhook_url")
|
| 38 |
+
id = data.get("id")
|
| 39 |
+
|
| 40 |
+
logger.info(
|
| 41 |
+
f"Job {job_id}: Received combine-audio request for {len(media_urls)} audio files"
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
try:
|
| 45 |
+
output_file = process_audio_concatenate(media_urls, job_id)
|
| 46 |
+
logger.info(f"Job {job_id}: Audio combination process completed successfully")
|
| 47 |
+
|
| 48 |
+
cloud_url = upload_file(output_file)
|
| 49 |
+
logger.info(
|
| 50 |
+
f"Job {job_id}: Combined audio uploaded to cloud storage: {cloud_url}"
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
return cloud_url, "/v1/audio/concatenate", 200
|
| 54 |
+
|
| 55 |
+
except Exception as e:
|
| 56 |
+
logger.error(f"Job {job_id}: Error during audio combination process - {str(e)}")
|
| 57 |
+
return str(e), "/v1/audio/concatenate", 500
|
routes/v1/code/execute/execute_python.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Stephen G. Pope
|
| 2 |
+
#
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by
|
| 5 |
+
# the Free Software Foundation; either version 2 of the License, or
|
| 6 |
+
# (at your option) any later version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful,
|
| 9 |
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 10 |
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 11 |
+
# GNU General Public License for more details.
|
| 12 |
+
#
|
| 13 |
+
# You should have received a copy of the GNU General Public License along
|
| 14 |
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
| 15 |
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
import os
|
| 20 |
+
import logging
|
| 21 |
+
from flask import Blueprint, request
|
| 22 |
+
from services.authentication import authenticate
|
| 23 |
+
from app_utils import validate_payload, queue_task_wrapper
|
| 24 |
+
import subprocess
|
| 25 |
+
import tempfile
|
| 26 |
+
import json
|
| 27 |
+
import textwrap
|
| 28 |
+
|
| 29 |
+
v1_code_execute_bp = Blueprint('v1_code_execute', __name__)
|
| 30 |
+
logger = logging.getLogger(__name__)
|
| 31 |
+
|
| 32 |
+
@v1_code_execute_bp.route('/v1/code/execute/python', methods=['POST'])
|
| 33 |
+
@authenticate
|
| 34 |
+
@validate_payload({
|
| 35 |
+
"type": "object",
|
| 36 |
+
"properties": {
|
| 37 |
+
"code": {"type": "string"},
|
| 38 |
+
"timeout": {"type": "integer", "minimum": 1, "maximum": 300},
|
| 39 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 40 |
+
"id": {"type": "string"}
|
| 41 |
+
},
|
| 42 |
+
"required": ["code"],
|
| 43 |
+
"additionalProperties": False
|
| 44 |
+
})
|
| 45 |
+
@queue_task_wrapper(bypass_queue=False)
|
| 46 |
+
def execute_python(job_id, data):
|
| 47 |
+
logger.info(f"Job {job_id}: Received Python code execution request")
|
| 48 |
+
|
| 49 |
+
try:
|
| 50 |
+
code = data['code']
|
| 51 |
+
timeout = data.get('timeout', 30)
|
| 52 |
+
|
| 53 |
+
# Indent user code
|
| 54 |
+
indented_code = textwrap.indent(code, ' ')
|
| 55 |
+
|
| 56 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as temp_file:
|
| 57 |
+
template = '''import sys
|
| 58 |
+
import json
|
| 59 |
+
from io import StringIO
|
| 60 |
+
import contextlib
|
| 61 |
+
|
| 62 |
+
@contextlib.contextmanager
|
| 63 |
+
def capture_output():
|
| 64 |
+
stdout, stderr = StringIO(), StringIO()
|
| 65 |
+
old_out, old_err = sys.stdout, sys.stderr
|
| 66 |
+
try:
|
| 67 |
+
sys.stdout, sys.stderr = stdout, stderr
|
| 68 |
+
yield stdout, stderr
|
| 69 |
+
finally:
|
| 70 |
+
sys.stdout, sys.stderr = old_out, old_err
|
| 71 |
+
|
| 72 |
+
def execute_code():
|
| 73 |
+
{}
|
| 74 |
+
|
| 75 |
+
with capture_output() as (stdout, stderr):
|
| 76 |
+
try:
|
| 77 |
+
result_value = execute_code()
|
| 78 |
+
except Exception as e:
|
| 79 |
+
print(f"Error: {{str(e)}}", file=sys.stderr)
|
| 80 |
+
result_value = None
|
| 81 |
+
|
| 82 |
+
result = {{
|
| 83 |
+
'stdout': stdout.getvalue(),
|
| 84 |
+
'stderr': stderr.getvalue(),
|
| 85 |
+
'return_value': result_value
|
| 86 |
+
}}
|
| 87 |
+
print(json.dumps(result))
|
| 88 |
+
'''
|
| 89 |
+
|
| 90 |
+
final_code = template.format(indented_code)
|
| 91 |
+
temp_file.write(final_code)
|
| 92 |
+
temp_file.flush()
|
| 93 |
+
|
| 94 |
+
# Log the generated code for debugging
|
| 95 |
+
logger.debug(f"Generated code:\n{final_code}")
|
| 96 |
+
|
| 97 |
+
try:
|
| 98 |
+
result = subprocess.run(
|
| 99 |
+
['python3', temp_file.name],
|
| 100 |
+
capture_output=True,
|
| 101 |
+
text=True,
|
| 102 |
+
timeout=timeout
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
try:
|
| 106 |
+
output = json.loads(result.stdout)
|
| 107 |
+
if result.returncode != 0 or output['stderr']:
|
| 108 |
+
return {
|
| 109 |
+
'error': output['stderr'] or 'Execution failed',
|
| 110 |
+
'stdout': output['stdout'],
|
| 111 |
+
'exit_code': result.returncode
|
| 112 |
+
}, '/v1/code/execute/python', 400
|
| 113 |
+
|
| 114 |
+
return {
|
| 115 |
+
'result': output['return_value'],
|
| 116 |
+
'stdout': output['stdout'],
|
| 117 |
+
'stderr': output['stderr'],
|
| 118 |
+
'exit_code': result.returncode
|
| 119 |
+
}, '/v1/code/execute/python', 200
|
| 120 |
+
|
| 121 |
+
except json.JSONDecodeError:
|
| 122 |
+
return {
|
| 123 |
+
'error': 'Failed to parse execution result',
|
| 124 |
+
'stdout': result.stdout,
|
| 125 |
+
'stderr': result.stderr,
|
| 126 |
+
'exit_code': result.returncode
|
| 127 |
+
}, '/v1/code/execute/python', 500
|
| 128 |
+
|
| 129 |
+
except subprocess.TimeoutExpired:
|
| 130 |
+
return {"error": f"Execution timed out after {timeout} seconds"}, '/v1/code/execute/python', 408
|
| 131 |
+
except subprocess.SubprocessError as e:
|
| 132 |
+
return {"error": f"Execution failed: {str(e)}"}, '/v1/code/execute/python', 500
|
| 133 |
+
|
| 134 |
+
except Exception as e:
|
| 135 |
+
logger.error(f"Job {job_id}: Error executing Python code: {str(e)}")
|
| 136 |
+
return {"error": str(e)}, '/v1/code/execute/python', 500
|
| 137 |
+
|
| 138 |
+
finally:
|
| 139 |
+
if 'temp_file' in locals():
|
| 140 |
+
os.unlink(temp_file.name)
|
routes/v1/ffmpeg/ffmpeg_compose.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Stephen G. Pope
|
| 2 |
+
#
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by
|
| 5 |
+
# the Free Software Foundation; either version 2 of the License, or
|
| 6 |
+
# (at your option) any later version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful,
|
| 9 |
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 10 |
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 11 |
+
# GNU General Public License for more details.
|
| 12 |
+
#
|
| 13 |
+
# You should have received a copy of the GNU General Public License along
|
| 14 |
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
| 15 |
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
import os
|
| 20 |
+
import logging
|
| 21 |
+
from flask import Blueprint, request, jsonify
|
| 22 |
+
from app_utils import *
|
| 23 |
+
from services.v1.ffmpeg.ffmpeg_compose import process_ffmpeg_compose
|
| 24 |
+
from services.authentication import authenticate
|
| 25 |
+
from services.cloud_storage import upload_file
|
| 26 |
+
|
| 27 |
+
v1_ffmpeg_compose_bp = Blueprint('v1_ffmpeg_compose', __name__)
|
| 28 |
+
logger = logging.getLogger(__name__)
|
| 29 |
+
|
| 30 |
+
@v1_ffmpeg_compose_bp.route('/v1/ffmpeg/compose', methods=['POST'])
|
| 31 |
+
@authenticate
|
| 32 |
+
@validate_payload({
|
| 33 |
+
"type": "object",
|
| 34 |
+
"properties": {
|
| 35 |
+
"inputs": {
|
| 36 |
+
"type": "array",
|
| 37 |
+
"items": {
|
| 38 |
+
"type": "object",
|
| 39 |
+
"properties": {
|
| 40 |
+
"file_url": {"type": "string", "format": "uri"},
|
| 41 |
+
"options": {
|
| 42 |
+
"type": "array",
|
| 43 |
+
"items": {
|
| 44 |
+
"type": "object",
|
| 45 |
+
"properties": {
|
| 46 |
+
"option": {"type": "string"},
|
| 47 |
+
"argument": {"type": ["string", "number", "null"]}
|
| 48 |
+
},
|
| 49 |
+
"required": ["option"]
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
},
|
| 53 |
+
"required": ["file_url"]
|
| 54 |
+
},
|
| 55 |
+
"minItems": 1
|
| 56 |
+
},
|
| 57 |
+
"filters": {
|
| 58 |
+
"type": "array",
|
| 59 |
+
"items": {
|
| 60 |
+
"type": "object",
|
| 61 |
+
"properties": {
|
| 62 |
+
"filter": {"type": "string"}
|
| 63 |
+
},
|
| 64 |
+
"required": ["filter"]
|
| 65 |
+
}
|
| 66 |
+
},
|
| 67 |
+
"outputs": {
|
| 68 |
+
"type": "array",
|
| 69 |
+
"items": {
|
| 70 |
+
"type": "object",
|
| 71 |
+
"properties": {
|
| 72 |
+
"options": {
|
| 73 |
+
"type": "array",
|
| 74 |
+
"items": {
|
| 75 |
+
"type": "object",
|
| 76 |
+
"properties": {
|
| 77 |
+
"option": {"type": "string"},
|
| 78 |
+
"argument": {"type": ["string", "number", "null"]}
|
| 79 |
+
},
|
| 80 |
+
"required": ["option"]
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
},
|
| 84 |
+
"required": ["options"]
|
| 85 |
+
},
|
| 86 |
+
"minItems": 1
|
| 87 |
+
},
|
| 88 |
+
"global_options": {
|
| 89 |
+
"type": "array",
|
| 90 |
+
"items": {
|
| 91 |
+
"type": "object",
|
| 92 |
+
"properties": {
|
| 93 |
+
"option": {"type": "string"},
|
| 94 |
+
"argument": {"type": ["string", "number", "null"]}
|
| 95 |
+
},
|
| 96 |
+
"required": ["option"]
|
| 97 |
+
}
|
| 98 |
+
},
|
| 99 |
+
"metadata": {
|
| 100 |
+
"type": "object",
|
| 101 |
+
"properties": {
|
| 102 |
+
"thumbnail": {"type": "boolean"},
|
| 103 |
+
"filesize": {"type": "boolean"},
|
| 104 |
+
"duration": {"type": "boolean"},
|
| 105 |
+
"bitrate": {"type": "boolean"},
|
| 106 |
+
"encoder": {"type": "boolean"}
|
| 107 |
+
}
|
| 108 |
+
},
|
| 109 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 110 |
+
"id": {"type": "string"}
|
| 111 |
+
},
|
| 112 |
+
"required": ["inputs", "outputs"],
|
| 113 |
+
"additionalProperties": False
|
| 114 |
+
})
|
| 115 |
+
@queue_task_wrapper(bypass_queue=False)
|
| 116 |
+
def ffmpeg_api(job_id, data):
|
| 117 |
+
logger.info(f"Job {job_id}: Received flexible FFmpeg request")
|
| 118 |
+
|
| 119 |
+
try:
|
| 120 |
+
output_filenames, metadata = process_ffmpeg_compose(data, job_id)
|
| 121 |
+
|
| 122 |
+
# Upload output files to GCP and create result array
|
| 123 |
+
output_urls = []
|
| 124 |
+
for i, output_filename in enumerate(output_filenames):
|
| 125 |
+
if os.path.exists(output_filename):
|
| 126 |
+
upload_url = upload_file(output_filename)
|
| 127 |
+
output_info = {"file_url": upload_url}
|
| 128 |
+
|
| 129 |
+
if metadata and i < len(metadata):
|
| 130 |
+
output_metadata = metadata[i]
|
| 131 |
+
if 'thumbnail' in output_metadata:
|
| 132 |
+
thumbnail_path = output_metadata['thumbnail']
|
| 133 |
+
if os.path.exists(thumbnail_path):
|
| 134 |
+
thumbnail_url = upload_file(thumbnail_path)
|
| 135 |
+
del output_metadata['thumbnail']
|
| 136 |
+
output_metadata['thumbnail_url'] = thumbnail_url
|
| 137 |
+
os.remove(thumbnail_path) # Clean up local thumbnail file
|
| 138 |
+
output_info.update(output_metadata)
|
| 139 |
+
|
| 140 |
+
output_urls.append(output_info)
|
| 141 |
+
os.remove(output_filename) # Clean up local output file after upload
|
| 142 |
+
else:
|
| 143 |
+
raise Exception(f"Expected output file {output_filename} not found")
|
| 144 |
+
|
| 145 |
+
return output_urls, "/v1/ffmpeg/compose", 200
|
| 146 |
+
|
| 147 |
+
except Exception as e:
|
| 148 |
+
logger.error(f"Job {job_id}: Error processing FFmpeg request - {str(e)}")
|
| 149 |
+
return str(e), "/v1/ffmpeg/compose", 500
|
routes/v1/image/convert/image_to_video.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Stephen G. Pope
|
| 2 |
+
#
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by
|
| 5 |
+
# the Free Software Foundation; either version 2 of the License, or
|
| 6 |
+
# (at your option) any later version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful,
|
| 9 |
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 10 |
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 11 |
+
# GNU General Public License for more details.
|
| 12 |
+
#
|
| 13 |
+
# You should have received a copy of the GNU General Public License along
|
| 14 |
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
| 15 |
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
from flask import Blueprint
|
| 20 |
+
from app_utils import *
|
| 21 |
+
import logging
|
| 22 |
+
from services.v1.image.convert.image_to_video import process_image_to_video
|
| 23 |
+
from services.authentication import authenticate
|
| 24 |
+
from services.cloud_storage import upload_file
|
| 25 |
+
|
| 26 |
+
v1_image_convert_video_bp = Blueprint('v1_image_convert_video', __name__)
|
| 27 |
+
logger = logging.getLogger(__name__)
|
| 28 |
+
|
| 29 |
+
@v1_image_convert_video_bp.route('/v1/image/convert/video', methods=['POST'])
|
| 30 |
+
@v1_image_convert_video_bp.route('/v1/image/transform/video', methods=['POST']) #depleft for backwards compatibility, do not use.
|
| 31 |
+
@authenticate
|
| 32 |
+
@validate_payload({
|
| 33 |
+
"type": "object",
|
| 34 |
+
"properties": {
|
| 35 |
+
"image_url": {"type": "string", "format": "uri"},
|
| 36 |
+
"length": {"type": "number", "minimum": 0.1, "maximum": 400},
|
| 37 |
+
"frame_rate": {"type": "integer", "minimum": 15, "maximum": 60},
|
| 38 |
+
"zoom_speed": {"type": "number", "minimum": 0, "maximum": 100},
|
| 39 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 40 |
+
"id": {"type": "string"}
|
| 41 |
+
},
|
| 42 |
+
"required": ["image_url"],
|
| 43 |
+
"additionalProperties": False
|
| 44 |
+
})
|
| 45 |
+
@queue_task_wrapper(bypass_queue=False)
|
| 46 |
+
def image_to_video(job_id, data):
|
| 47 |
+
image_url = data.get('image_url')
|
| 48 |
+
length = data.get('length', 5)
|
| 49 |
+
frame_rate = data.get('frame_rate', 30)
|
| 50 |
+
zoom_speed = data.get('zoom_speed', 3) / 100
|
| 51 |
+
webhook_url = data.get('webhook_url')
|
| 52 |
+
id = data.get('id')
|
| 53 |
+
|
| 54 |
+
logger.info(f"Job {job_id}: Received image to video request for {image_url}")
|
| 55 |
+
|
| 56 |
+
try:
|
| 57 |
+
# Process image to video conversion
|
| 58 |
+
output_filename = process_image_to_video(
|
| 59 |
+
image_url, length, frame_rate, zoom_speed, job_id, webhook_url
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
# Upload the resulting file using the unified upload_file() method
|
| 63 |
+
cloud_url = upload_file(output_filename)
|
| 64 |
+
|
| 65 |
+
# Log the successful upload
|
| 66 |
+
logger.info(f"Job {job_id}: Converted video uploaded to cloud storage: {cloud_url}")
|
| 67 |
+
|
| 68 |
+
# Return the cloud URL for the uploaded file
|
| 69 |
+
return cloud_url, "/v1/image/convert/video", 200
|
| 70 |
+
|
| 71 |
+
except Exception as e:
|
| 72 |
+
logger.error(f"Job {job_id}: Error processing image to video: {str(e)}", exc_info=True)
|
| 73 |
+
return str(e), "/v1/image/convert/video", 500
|
routes/v1/image/screenshot_webpage.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Stephen G. Pope
|
| 2 |
+
#
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by
|
| 5 |
+
# the Free Software Foundation; either version 2 of the License, or
|
| 6 |
+
# (at your option) any later version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful,
|
| 9 |
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 10 |
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 11 |
+
# GNU General Public License for more details.
|
| 12 |
+
#
|
| 13 |
+
# You should have received a copy of the GNU General Public License along
|
| 14 |
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
| 15 |
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
# Author: Harrison Fisher (https://github.com/HarrisonFisher)
|
| 19 |
+
# Date: April 2025
|
| 20 |
+
# Created new route: /v1/playwright/screenshot
|
| 21 |
+
|
| 22 |
+
from flask import Blueprint, request, jsonify
|
| 23 |
+
from app_utils import *
|
| 24 |
+
from app_utils import validate_payload, queue_task_wrapper
|
| 25 |
+
import logging
|
| 26 |
+
import os
|
| 27 |
+
from services.v1.image.screenshot_webpage import take_screenshot
|
| 28 |
+
from services.authentication import authenticate
|
| 29 |
+
from services.cloud_storage import upload_file
|
| 30 |
+
from playwright.sync_api import sync_playwright
|
| 31 |
+
from io import BytesIO
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
v1_image_screenshot_webpage_bp = Blueprint('v1_image_screenshot_webpage', __name__)
|
| 35 |
+
logger = logging.getLogger(__name__)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
@v1_image_screenshot_webpage_bp.route('/v1/image/screenshot/webpage', methods=['POST'])
|
| 39 |
+
@authenticate
|
| 40 |
+
@validate_payload({
|
| 41 |
+
"type": "object",
|
| 42 |
+
"properties": {
|
| 43 |
+
"url": {"type": "string", "format": "uri"},
|
| 44 |
+
"html": {"type": "string"},
|
| 45 |
+
"viewport_width": {"type": "integer", "minimum": 1},
|
| 46 |
+
"viewport_height": {"type": "integer", "minimum": 1},
|
| 47 |
+
"full_page": {"type": "boolean", "default": False},
|
| 48 |
+
"format": {"type": "string", "enum": ["png", "jpeg"], "default": "png"},
|
| 49 |
+
"delay": {"type": "integer", "minimum": 0},
|
| 50 |
+
"device_scale_factor": {"type": "number", "minimum": 0.1},
|
| 51 |
+
"user_agent": {"type": "string"},
|
| 52 |
+
"cookies": {
|
| 53 |
+
"type": "array",
|
| 54 |
+
"items": {
|
| 55 |
+
"type": "object",
|
| 56 |
+
"required": ["name", "value", "domain"],
|
| 57 |
+
"properties": {
|
| 58 |
+
"name": {"type": "string"},
|
| 59 |
+
"value": {"type": "string"},
|
| 60 |
+
"domain": {"type": "string"},
|
| 61 |
+
"path": {"type": "string", "default": "/"},
|
| 62 |
+
},
|
| 63 |
+
"additionalProperties": True
|
| 64 |
+
}
|
| 65 |
+
},
|
| 66 |
+
"headers": {
|
| 67 |
+
"type": "object",
|
| 68 |
+
"additionalProperties": {"type": "string"}
|
| 69 |
+
},
|
| 70 |
+
"quality": {"type": "integer", "minimum": 0, "maximum": 100},
|
| 71 |
+
"clip": {
|
| 72 |
+
"type": "object",
|
| 73 |
+
"required": ["x", "y", "width", "height"],
|
| 74 |
+
"properties": {
|
| 75 |
+
"x": {"type": "number"},
|
| 76 |
+
"y": {"type": "number"},
|
| 77 |
+
"width": {"type": "number", "exclusiveMinimum": 0},
|
| 78 |
+
"height": {"type": "number", "exclusiveMinimum": 0}
|
| 79 |
+
}
|
| 80 |
+
},
|
| 81 |
+
"timeout": {"type": "integer", "minimum": 100},
|
| 82 |
+
"wait_until": {"type": "string", "enum": ["load", "domcontentloaded", "networkidle", "networkidle2"], "default": "load"},
|
| 83 |
+
"wait_for_selector": {"type": "string"},
|
| 84 |
+
"emulate": {
|
| 85 |
+
"type": "object",
|
| 86 |
+
"properties": {
|
| 87 |
+
"color_scheme": {
|
| 88 |
+
"type": "string",
|
| 89 |
+
"enum": ["light", "dark"]
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
},
|
| 93 |
+
"omit_background": {"type": "boolean", "default": False},
|
| 94 |
+
"selector": {"type": "string"},
|
| 95 |
+
"js": {"type": "string"},
|
| 96 |
+
"css": {"type": "string"},
|
| 97 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 98 |
+
"id": {"type": "string"}
|
| 99 |
+
},
|
| 100 |
+
"oneOf": [
|
| 101 |
+
{"required": ["url"]},
|
| 102 |
+
{"required": ["html"]}
|
| 103 |
+
],
|
| 104 |
+
"not": {"required": ["url", "html"]},
|
| 105 |
+
"additionalProperties": False
|
| 106 |
+
})
|
| 107 |
+
@queue_task_wrapper(bypass_queue=False)
|
| 108 |
+
def screenshot(job_id, data):
|
| 109 |
+
logger.info(f"Job {job_id}: Received screenshot request for {data.get('url')}")
|
| 110 |
+
try:
|
| 111 |
+
screenshot_io = take_screenshot(data, job_id)
|
| 112 |
+
if isinstance(screenshot_io, dict) and 'error' in screenshot_io:
|
| 113 |
+
logger.error(f"Job {job_id}: Screenshot error: {screenshot_io['error']}")
|
| 114 |
+
return {"error": screenshot_io['error']}, "/v1/image/screenshot/webpage", 400
|
| 115 |
+
format = data.get("format", "png")
|
| 116 |
+
temp_file_path = f"{job_id}_screenshot.{format}"
|
| 117 |
+
with open(temp_file_path, "wb") as temp_file:
|
| 118 |
+
temp_file.write(screenshot_io.read())
|
| 119 |
+
cloud_url = upload_file(temp_file_path)
|
| 120 |
+
os.remove(temp_file_path)
|
| 121 |
+
logger.info(f"Job {job_id}: Screenshot successfully processed and uploaded.")
|
| 122 |
+
return cloud_url, "/v1/image/screenshot/webpage", 200
|
| 123 |
+
except Exception as e:
|
| 124 |
+
logger.error(f"Job {job_id}: Error processing screenshot: {str(e)}", exc_info=True)
|
| 125 |
+
return {"error": str(e)}, "/v1/image/screenshot/webpage", 500
|
routes/v1/media/convert/media_convert.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Stephen G. Pope
|
| 2 |
+
#
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by
|
| 5 |
+
# the Free Software Foundation; either version 2 of the License, or
|
| 6 |
+
# (at your option) any later version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful,
|
| 9 |
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 10 |
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 11 |
+
# GNU General Public License for more details.
|
| 12 |
+
#
|
| 13 |
+
# You should have received a copy of the GNU General Public License along
|
| 14 |
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
| 15 |
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| 16 |
+
|
| 17 |
+
from flask import Blueprint, jsonify
|
| 18 |
+
from app_utils import validate_payload, queue_task_wrapper
|
| 19 |
+
import logging
|
| 20 |
+
from services.v1.media.convert.media_convert import process_media_convert
|
| 21 |
+
from services.authentication import authenticate
|
| 22 |
+
from services.cloud_storage import upload_file
|
| 23 |
+
import os
|
| 24 |
+
|
| 25 |
+
v1_media_convert_bp = Blueprint('v1_media_convert', __name__)
|
| 26 |
+
logger = logging.getLogger(__name__)
|
| 27 |
+
|
| 28 |
+
@v1_media_convert_bp.route('/v1/media/convert', methods=['POST'])
|
| 29 |
+
@authenticate
|
| 30 |
+
@validate_payload({
|
| 31 |
+
"type": "object",
|
| 32 |
+
"properties": {
|
| 33 |
+
"media_url": {"type": "string", "format": "uri"},
|
| 34 |
+
"format": {"type": "string"},
|
| 35 |
+
"video_codec": {"type": "string"},
|
| 36 |
+
"video_preset": {"type": "string"},
|
| 37 |
+
"video_crf": {"type": "number", "minimum": 0, "maximum": 51},
|
| 38 |
+
"audio_codec": {"type": "string"},
|
| 39 |
+
"audio_bitrate": {"type": "string"},
|
| 40 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 41 |
+
"id": {"type": "string"}
|
| 42 |
+
},
|
| 43 |
+
"required": ["media_url", "format"],
|
| 44 |
+
"additionalProperties": False
|
| 45 |
+
})
|
| 46 |
+
@queue_task_wrapper(bypass_queue=False)
|
| 47 |
+
def convert_media_format(job_id, data):
|
| 48 |
+
media_url = data['media_url']
|
| 49 |
+
output_format = data['format']
|
| 50 |
+
video_codec = data.get('video_codec', 'libx264')
|
| 51 |
+
video_preset = data.get('video_preset', 'medium')
|
| 52 |
+
video_crf = data.get('video_crf', 23)
|
| 53 |
+
audio_codec = data.get('audio_codec', 'aac')
|
| 54 |
+
audio_bitrate = data.get('audio_bitrate', '128k')
|
| 55 |
+
webhook_url = data.get('webhook_url')
|
| 56 |
+
id = data.get('id')
|
| 57 |
+
|
| 58 |
+
logger.info(f"Job {job_id}: Received media conversion request for media URL: {media_url} to format: {output_format}")
|
| 59 |
+
|
| 60 |
+
try:
|
| 61 |
+
output_file = process_media_convert(
|
| 62 |
+
media_url,
|
| 63 |
+
job_id,
|
| 64 |
+
output_format,
|
| 65 |
+
video_codec,
|
| 66 |
+
video_preset,
|
| 67 |
+
video_crf,
|
| 68 |
+
audio_codec,
|
| 69 |
+
audio_bitrate,
|
| 70 |
+
webhook_url
|
| 71 |
+
)
|
| 72 |
+
logger.info(f"Job {job_id}: Media format conversion completed successfully")
|
| 73 |
+
|
| 74 |
+
cloud_url = upload_file(output_file)
|
| 75 |
+
logger.info(f"Job {job_id}: Converted media uploaded to cloud storage: {cloud_url}")
|
| 76 |
+
|
| 77 |
+
return cloud_url, "/v1/media/convert", 200
|
| 78 |
+
|
| 79 |
+
except Exception as e:
|
| 80 |
+
logger.error(f"Job {job_id}: Error during media conversion process - {str(e)}")
|
| 81 |
+
return {"error": str(e)}, "/v1/media/convert", 500
|
routes/v1/media/convert/media_to_mp3.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Stephen G. Pope
|
| 2 |
+
#
|
| 3 |
+
# This program is free software; you can redistribute it and/or modify
|
| 4 |
+
# it under the terms of the GNU General Public License as published by
|
| 5 |
+
# the Free Software Foundation; either version 2 of the License, or
|
| 6 |
+
# (at your option) any later version.
|
| 7 |
+
#
|
| 8 |
+
# This program is distributed in the hope that it will be useful,
|
| 9 |
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 10 |
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 11 |
+
# GNU General Public License for more details.
|
| 12 |
+
#
|
| 13 |
+
# You should have received a copy of the GNU General Public License along
|
| 14 |
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
| 15 |
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
# routes/media_to_mp3.py
|
| 20 |
+
from flask import Blueprint, current_app
|
| 21 |
+
from app_utils import *
|
| 22 |
+
import logging
|
| 23 |
+
from services.v1.media.convert.media_to_mp3 import process_media_to_mp3
|
| 24 |
+
from services.authentication import authenticate
|
| 25 |
+
from services.cloud_storage import upload_file
|
| 26 |
+
import os
|
| 27 |
+
|
| 28 |
+
v1_media_convert_mp3_bp = Blueprint('v1_media_convert_mp3', __name__)
|
| 29 |
+
logger = logging.getLogger(__name__)
|
| 30 |
+
|
| 31 |
+
@v1_media_convert_mp3_bp.route('/v1/media/convert/mp3', methods=['POST'])
|
| 32 |
+
@v1_media_convert_mp3_bp.route('/v1/media/transform/mp3', methods=['POST']) #depleft for backwards compatibility, do not use.
|
| 33 |
+
@authenticate
|
| 34 |
+
@validate_payload({
|
| 35 |
+
"type": "object",
|
| 36 |
+
"properties": {
|
| 37 |
+
"media_url": {"type": "string", "format": "uri"},
|
| 38 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 39 |
+
"id": {"type": "string"},
|
| 40 |
+
"bitrate": {"type": "string", "pattern": "^[0-9]+k$"},
|
| 41 |
+
"sample_rate": {"type": "number"}
|
| 42 |
+
},
|
| 43 |
+
"required": ["media_url"],
|
| 44 |
+
"additionalProperties": False
|
| 45 |
+
})
|
| 46 |
+
@queue_task_wrapper(bypass_queue=False)
|
| 47 |
+
def convert_media_to_mp3(job_id, data):
|
| 48 |
+
media_url = data['media_url']
|
| 49 |
+
webhook_url = data.get('webhook_url')
|
| 50 |
+
id = data.get('id')
|
| 51 |
+
bitrate = data.get('bitrate', '128k')
|
| 52 |
+
sample_rate = data.get('sample_rate')
|
| 53 |
+
|
| 54 |
+
logger.info(f"Job {job_id}: Received media-to-mp3 request for media URL: {media_url}")
|
| 55 |
+
|
| 56 |
+
try:
|
| 57 |
+
output_file = process_media_to_mp3(media_url, job_id, bitrate, sample_rate)
|
| 58 |
+
logger.info(f"Job {job_id}: Media conversion process completed successfully")
|
| 59 |
+
|
| 60 |
+
cloud_url = upload_file(output_file)
|
| 61 |
+
logger.info(f"Job {job_id}: Converted media uploaded to cloud storage: {cloud_url}")
|
| 62 |
+
|
| 63 |
+
return cloud_url, "/v1/media/transform/mp3", 200
|
| 64 |
+
|
| 65 |
+
except Exception as e:
|
| 66 |
+
logger.error(f"Job {job_id}: Error during media conversion process - {str(e)}")
|
| 67 |
+
return str(e), "/v1/media/transform/mp3", 500
|
routes/v1/media/download.py
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Blueprint
|
| 2 |
+
from app_utils import *
|
| 3 |
+
import logging
|
| 4 |
+
import os
|
| 5 |
+
import yt_dlp
|
| 6 |
+
import tempfile
|
| 7 |
+
from werkzeug.utils import secure_filename
|
| 8 |
+
import uuid
|
| 9 |
+
from services.cloud_storage import upload_file
|
| 10 |
+
from services.authentication import authenticate
|
| 11 |
+
from services.file_management import download_file
|
| 12 |
+
from urllib.parse import quote, urlparse
|
| 13 |
+
|
| 14 |
+
v1_media_download_bp = Blueprint('v1_media_download', __name__)
|
| 15 |
+
logger = logging.getLogger(__name__)
|
| 16 |
+
|
| 17 |
+
@v1_media_download_bp.route('/v1/BETA/media/download', methods=['POST'])
|
| 18 |
+
@authenticate
|
| 19 |
+
@validate_payload({
|
| 20 |
+
"type": "object",
|
| 21 |
+
"properties": {
|
| 22 |
+
"media_url": {"type": "string", "format": "uri"},
|
| 23 |
+
"webhook_url": {"type": "string", "format": "uri"},
|
| 24 |
+
"id": {"type": "string"},
|
| 25 |
+
"cookie": {"type": "string", "description": "Path to cookie file, URL to cookie file, or cookie string in Netscape format"},
|
| 26 |
+
"cloud_upload": {"type": "boolean"},
|
| 27 |
+
"format": {
|
| 28 |
+
"type": "object",
|
| 29 |
+
"properties": {
|
| 30 |
+
"quality": {"type": "string"},
|
| 31 |
+
"format_id": {"type": "string"},
|
| 32 |
+
"resolution": {"type": "string"},
|
| 33 |
+
"video_codec": {"type": "string"},
|
| 34 |
+
"audio_codec": {"type": "string"}
|
| 35 |
+
}
|
| 36 |
+
},
|
| 37 |
+
"audio": {
|
| 38 |
+
"type": "object",
|
| 39 |
+
"properties": {
|
| 40 |
+
"extract": {"type": "boolean"},
|
| 41 |
+
"format": {"type": "string"},
|
| 42 |
+
"quality": {"type": "string"}
|
| 43 |
+
}
|
| 44 |
+
},
|
| 45 |
+
"thumbnails": {
|
| 46 |
+
"type": "object",
|
| 47 |
+
"properties": {
|
| 48 |
+
"download": {"type": "boolean"},
|
| 49 |
+
"download_all": {"type": "boolean"},
|
| 50 |
+
"formats": {"type": "array", "items": {"type": "string"}},
|
| 51 |
+
"convert": {"type": "boolean"},
|
| 52 |
+
"embed_in_audio": {"type": "boolean"}
|
| 53 |
+
}
|
| 54 |
+
},
|
| 55 |
+
"subtitles": {
|
| 56 |
+
"type": "object",
|
| 57 |
+
"properties": {
|
| 58 |
+
"download": {"type": "boolean"},
|
| 59 |
+
"languages": {"type": "array", "items": {"type": "string"}},
|
| 60 |
+
"formats": {"type": "array", "items": {"type": "string"}}
|
| 61 |
+
}
|
| 62 |
+
},
|
| 63 |
+
"download": {
|
| 64 |
+
"type": "object",
|
| 65 |
+
"properties": {
|
| 66 |
+
"max_filesize": {"type": "integer"},
|
| 67 |
+
"rate_limit": {"type": "string"},
|
| 68 |
+
"retries": {"type": "integer"}
|
| 69 |
+
}
|
| 70 |
+
}
|
| 71 |
+
},
|
| 72 |
+
"required": ["media_url"],
|
| 73 |
+
"additionalProperties": False
|
| 74 |
+
})
|
| 75 |
+
@queue_task_wrapper(bypass_queue=False)
|
| 76 |
+
def download_media(job_id, data):
|
| 77 |
+
media_url = data['media_url']
|
| 78 |
+
cookie = data.get('cookie')
|
| 79 |
+
|
| 80 |
+
format_options = data.get('format', {})
|
| 81 |
+
audio_options = data.get('audio', {})
|
| 82 |
+
thumbnail_options = data.get('thumbnails', {})
|
| 83 |
+
subtitle_options = data.get('subtitles', {})
|
| 84 |
+
download_options = data.get('download', {})
|
| 85 |
+
|
| 86 |
+
logger.info(f"Job {job_id}: Received download request for {media_url}")
|
| 87 |
+
|
| 88 |
+
try:
|
| 89 |
+
# Create a temporary directory for downloads
|
| 90 |
+
with tempfile.TemporaryDirectory() as temp_dir:
|
| 91 |
+
# Configure yt-dlp options
|
| 92 |
+
ydl_opts = {
|
| 93 |
+
'format': 'best', # Download best quality
|
| 94 |
+
'outtmpl': os.path.join(temp_dir, '%(title)s.%(ext)s'),
|
| 95 |
+
'quiet': True,
|
| 96 |
+
'no_warnings': True,
|
| 97 |
+
'download': data.get('cloud_upload', True)
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
# Add cookies if provided
|
| 101 |
+
if cookie:
|
| 102 |
+
if os.path.isfile(cookie):
|
| 103 |
+
ydl_opts['cookiefile'] = cookie
|
| 104 |
+
elif urlparse(cookie).scheme in ('http', 'https'):
|
| 105 |
+
# If cookie is a URL, download it first
|
| 106 |
+
ydl_opts['cookiefile'] = download_file(cookie, temp_dir)
|
| 107 |
+
else:
|
| 108 |
+
# If cookie is a string, write it to a temporary file
|
| 109 |
+
cookie_file = os.path.join(temp_dir, 'cookies.txt')
|
| 110 |
+
with open(cookie_file, 'w') as f:
|
| 111 |
+
f.write(cookie)
|
| 112 |
+
ydl_opts['cookiefile'] = cookie_file
|
| 113 |
+
|
| 114 |
+
# Add format options if specified
|
| 115 |
+
if format_options:
|
| 116 |
+
format_str = []
|
| 117 |
+
if format_options.get('quality'):
|
| 118 |
+
format_str.append(format_options['quality'])
|
| 119 |
+
if format_options.get('format_id'):
|
| 120 |
+
format_str.append(format_options['format_id'])
|
| 121 |
+
if format_options.get('resolution'):
|
| 122 |
+
format_str.append(format_options['resolution'])
|
| 123 |
+
if format_options.get('video_codec'):
|
| 124 |
+
format_str.append(format_options['video_codec'])
|
| 125 |
+
if format_options.get('audio_codec'):
|
| 126 |
+
format_str.append(format_options['audio_codec'])
|
| 127 |
+
if format_str:
|
| 128 |
+
ydl_opts['format'] = '+'.join(format_str)
|
| 129 |
+
|
| 130 |
+
# Add audio options if specified
|
| 131 |
+
if audio_options:
|
| 132 |
+
if audio_options.get('extract'):
|
| 133 |
+
ydl_opts['extract_audio'] = True
|
| 134 |
+
if audio_options.get('format'):
|
| 135 |
+
ydl_opts['audio_format'] = audio_options['format']
|
| 136 |
+
if audio_options.get('quality'):
|
| 137 |
+
ydl_opts['audio_quality'] = audio_options['quality']
|
| 138 |
+
|
| 139 |
+
# Add thumbnail options if specified
|
| 140 |
+
if thumbnail_options:
|
| 141 |
+
ydl_opts['writesubtitles'] = thumbnail_options.get('download', False)
|
| 142 |
+
ydl_opts['writeallsubtitles'] = thumbnail_options.get('download_all', False)
|
| 143 |
+
if thumbnail_options.get('formats'):
|
| 144 |
+
ydl_opts['subtitleslangs'] = thumbnail_options['formats']
|
| 145 |
+
ydl_opts['convert_thumbnails'] = thumbnail_options.get('convert', False)
|
| 146 |
+
ydl_opts['embed_thumbnail_in_audio'] = thumbnail_options.get('embed_in_audio', False)
|
| 147 |
+
|
| 148 |
+
# Add subtitle options if specified
|
| 149 |
+
if subtitle_options:
|
| 150 |
+
ydl_opts['writesubtitles'] = subtitle_options.get('download', False)
|
| 151 |
+
if subtitle_options.get('languages'):
|
| 152 |
+
ydl_opts['subtitleslangs'] = subtitle_options['languages']
|
| 153 |
+
if subtitle_options.get('formats'):
|
| 154 |
+
ydl_opts['subtitlesformat'] = subtitle_options['formats']
|
| 155 |
+
|
| 156 |
+
# Add download options if specified
|
| 157 |
+
if download_options:
|
| 158 |
+
if download_options.get('max_filesize'):
|
| 159 |
+
ydl_opts['max_filesize'] = download_options['max_filesize']
|
| 160 |
+
if download_options.get('rate_limit'):
|
| 161 |
+
ydl_opts['limit_rate'] = download_options['rate_limit']
|
| 162 |
+
if download_options.get('retries'):
|
| 163 |
+
ydl_opts['retries'] = download_options['retries']
|
| 164 |
+
|
| 165 |
+
# Download the media
|
| 166 |
+
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
| 167 |
+
info = ydl.extract_info(media_url, download=data.get('cloud_upload', True))
|
| 168 |
+
|
| 169 |
+
if not data.get('cloud_upload', True):
|
| 170 |
+
media_url = info['url']
|
| 171 |
+
else:
|
| 172 |
+
filename = ydl.prepare_filename(info)
|
| 173 |
+
# Upload to cloud storage
|
| 174 |
+
media_url = upload_file(filename)
|
| 175 |
+
# Clean up the temporary file
|
| 176 |
+
os.remove(filename)
|
| 177 |
+
|
| 178 |
+
# Prepare response
|
| 179 |
+
response = {
|
| 180 |
+
"media": {
|
| 181 |
+
"media_url": media_url,
|
| 182 |
+
"title": info.get('title'),
|
| 183 |
+
"format_id": info.get('format_id'),
|
| 184 |
+
"ext": info.get('ext'),
|
| 185 |
+
"resolution": info.get('resolution'),
|
| 186 |
+
"filesize": info.get('filesize'),
|
| 187 |
+
"width": info.get('width'),
|
| 188 |
+
"height": info.get('height'),
|
| 189 |
+
"fps": info.get('fps'),
|
| 190 |
+
"video_codec": info.get('vcodec'),
|
| 191 |
+
"audio_codec": info.get('acodec'),
|
| 192 |
+
"upload_date": info.get('upload_date'),
|
| 193 |
+
"duration": info.get('duration'),
|
| 194 |
+
"view_count": info.get('view_count'),
|
| 195 |
+
"uploader": info.get('uploader'),
|
| 196 |
+
"uploader_id": info.get('uploader_id'),
|
| 197 |
+
"description": info.get('description')
|
| 198 |
+
}
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
# Add thumbnails if available and requested
|
| 202 |
+
if info.get('thumbnails') and thumbnail_options.get('download', False):
|
| 203 |
+
response["thumbnails"] = []
|
| 204 |
+
for thumbnail in info['thumbnails']:
|
| 205 |
+
if thumbnail.get('url'):
|
| 206 |
+
try:
|
| 207 |
+
# Download the thumbnail first
|
| 208 |
+
thumbnail_path = download_file(thumbnail['url'], temp_dir)
|
| 209 |
+
# Upload to cloud storage
|
| 210 |
+
thumbnail_url = upload_file(thumbnail_path)
|
| 211 |
+
# Clean up the temporary thumbnail file
|
| 212 |
+
os.remove(thumbnail_path)
|
| 213 |
+
|
| 214 |
+
response["thumbnails"].append({
|
| 215 |
+
"id": thumbnail.get('id', 'default'),
|
| 216 |
+
"image_url": thumbnail_url,
|
| 217 |
+
"width": thumbnail.get('width'),
|
| 218 |
+
"height": thumbnail.get('height'),
|
| 219 |
+
"original_format": thumbnail.get('ext'),
|
| 220 |
+
"converted": thumbnail.get('converted', False)
|
| 221 |
+
})
|
| 222 |
+
except Exception as e:
|
| 223 |
+
logger.error(f"Error processing thumbnail: {str(e)}")
|
| 224 |
+
continue
|
| 225 |
+
|
| 226 |
+
return response, "/v1/media/download", 200
|
| 227 |
+
|
| 228 |
+
except Exception as e:
|
| 229 |
+
logger.error(f"Job {job_id}: Error during download process - {str(e)}")
|
| 230 |
+
return str(e), "/v1/media/download", 500
|